From ac874fe6d3b1525b83873c2eccd814d5d1c6bc70 Mon Sep 17 00:00:00 2001 From: Elad Bezalel Date: Tue, 13 Jun 2017 20:12:59 +0300 Subject: [PATCH] feat(directionality): a provider to get the overall directionality - Looks at the `html` and `body` elements for `dir` attribute and sets the Directionality service value to it - Whenever someone would try to inject Directionality - if there's a Dir directive up the dom tree it would be provided fixes #3600 --- src/lib/autocomplete/autocomplete-trigger.ts | 5 +- src/lib/autocomplete/autocomplete.spec.ts | 6 +- src/lib/core/bidi/dir.ts | 58 +++++++++ src/lib/core/bidi/directionality.spec.ts | 114 ++++++++++++++++++ src/lib/core/bidi/directionality.ts | 40 ++++++ src/lib/core/bidi/index.ts | 25 ++++ .../core/common-behaviors/common-module.ts | 5 +- src/lib/core/core.ts | 8 +- .../core/overlay/overlay-directives.spec.ts | 4 +- src/lib/core/overlay/overlay-directives.ts | 6 +- src/lib/core/overlay/overlay-state.ts | 4 +- src/lib/core/rtl/dir.ts | 53 -------- src/lib/datepicker/datepicker.ts | 4 +- src/lib/dialog/dialog-config.ts | 4 +- src/lib/grid-list/grid-list.ts | 4 +- src/lib/menu/menu-trigger.ts | 9 +- src/lib/menu/menu.spec.ts | 8 +- src/lib/module.ts | 6 +- src/lib/select/select.spec.ts | 4 +- src/lib/select/select.ts | 4 +- src/lib/sidenav/sidenav.ts | 6 +- src/lib/slider/index.ts | 4 +- src/lib/slider/slider.spec.ts | 4 +- src/lib/slider/slider.ts | 5 +- src/lib/snack-bar/snack-bar-config.ts | 4 +- src/lib/tabs/tab-body.spec.ts | 8 +- src/lib/tabs/tab-body.ts | 7 +- src/lib/tabs/tab-header.spec.ts | 18 ++- src/lib/tabs/tab-header.ts | 15 ++- src/lib/tabs/tab-nav-bar/tab-nav-bar.spec.ts | 11 +- src/lib/tabs/tab-nav-bar/tab-nav-bar.ts | 6 +- src/lib/tooltip/tooltip.spec.ts | 6 +- src/lib/tooltip/tooltip.ts | 7 +- 33 files changed, 333 insertions(+), 139 deletions(-) create mode 100644 src/lib/core/bidi/dir.ts create mode 100644 src/lib/core/bidi/directionality.spec.ts create mode 100644 src/lib/core/bidi/directionality.ts create mode 100644 src/lib/core/bidi/index.ts delete mode 100644 src/lib/core/rtl/dir.ts diff --git a/src/lib/autocomplete/autocomplete-trigger.ts b/src/lib/autocomplete/autocomplete-trigger.ts index 4b379c859ddc..9f3c600bed2e 100644 --- a/src/lib/autocomplete/autocomplete-trigger.ts +++ b/src/lib/autocomplete/autocomplete-trigger.ts @@ -20,7 +20,7 @@ import {ConnectedPositionStrategy} from '../core/overlay/position/connected-posi import {Observable} from 'rxjs/Observable'; import {MdOptionSelectionChange, MdOption} from '../core/option/option'; import {ENTER, UP_ARROW, DOWN_ARROW, ESCAPE} from '../core/keyboard/keycodes'; -import {Dir} from '../core/rtl/dir'; +import {Directionality} from '../core/bidi/index'; import {MdInputContainer} from '../input/input-container'; import {Subscription} from 'rxjs/Subscription'; import 'rxjs/add/observable/merge'; @@ -112,8 +112,9 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy { constructor(private _element: ElementRef, private _overlay: Overlay, private _viewContainerRef: ViewContainerRef, + private _zone: NgZone, private _changeDetectorRef: ChangeDetectorRef, - @Optional() private _dir: Dir, private _zone: NgZone, + @Optional() private _dir: Directionality, @Optional() @Host() private _inputContainer: MdInputContainer, @Optional() @Inject(DOCUMENT) private _document: any) {} diff --git a/src/lib/autocomplete/autocomplete.spec.ts b/src/lib/autocomplete/autocomplete.spec.ts index f3e730346d9a..f3b03b8fa090 100644 --- a/src/lib/autocomplete/autocomplete.spec.ts +++ b/src/lib/autocomplete/autocomplete.spec.ts @@ -19,7 +19,7 @@ import { } from './index'; import {OverlayContainer} from '../core/overlay/overlay-container'; import {MdInputModule} from '../input/index'; -import {Dir, LayoutDirection} from '../core/rtl/dir'; +import {Directionality, Direction} from '../core/bidi/index'; import {Subscription} from 'rxjs/Subscription'; import {ENTER, DOWN_ARROW, SPACE, UP_ARROW, ESCAPE} from '../core/keyboard/keycodes'; import {MdOption} from '../core/option/option'; @@ -35,7 +35,7 @@ import 'rxjs/add/operator/map'; describe('MdAutocomplete', () => { let overlayContainerElement: HTMLElement; - let dir: LayoutDirection; + let dir: Direction; let scrolledSubject = new Subject(); beforeEach(async(() => { @@ -70,7 +70,7 @@ describe('MdAutocomplete', () => { return {getContainerElement: () => overlayContainerElement}; }}, - {provide: Dir, useFactory: () => ({value: dir})}, + {provide: Directionality, useFactory: () => ({value: dir})}, {provide: ScrollDispatcher, useFactory: () => { return {scrolled: (_delay: number, callback: () => any) => { return scrolledSubject.asObservable().subscribe(callback); diff --git a/src/lib/core/bidi/dir.ts b/src/lib/core/bidi/dir.ts new file mode 100644 index 000000000000..e9ae3bd29464 --- /dev/null +++ b/src/lib/core/bidi/dir.ts @@ -0,0 +1,58 @@ +import { + Directive, + HostBinding, + Output, + Input, + EventEmitter +} from '@angular/core'; + +import {Direction, Directionality} from './directionality'; + +/** + * Directive to listen for changes of direction of part of the DOM. + * + * Would provide itself in case a component looks for the Directionality service + */ +@Directive({ + selector: '[dir]', + // TODO(hansl): maybe `$implicit` isn't the best option here, but for now that's the best we got. + exportAs: '$implicit', + providers: [ + {provide: Directionality, useExisting: Dir} + ] +}) +export class Dir implements Directionality { + /** Layout direction of the element. */ + _dir: Direction = 'ltr'; + + /** Whether the `value` has been set to its initial value. */ + private _isInitialized: boolean = false; + + /** Event emitted when the direction changes. */ + @Output('dirChange') change = new EventEmitter(); + + /** @docs-private */ + @HostBinding('attr.dir') + @Input('dir') + get dir(): Direction { + return this._dir; + } + + set dir(v: Direction) { + let old = this._dir; + this._dir = v; + if (old !== this._dir && this._isInitialized) { + this.change.emit(); + } + } + + /** Current layout direction of the element. */ + get value(): Direction { return this.dir; } + set value(v: Direction) { this.dir = v; } + + /** Initialize once default value has been set. */ + ngAfterContentInit() { + this._isInitialized = true; + } +} + diff --git a/src/lib/core/bidi/directionality.spec.ts b/src/lib/core/bidi/directionality.spec.ts new file mode 100644 index 000000000000..cf01d02143a6 --- /dev/null +++ b/src/lib/core/bidi/directionality.spec.ts @@ -0,0 +1,114 @@ +import {async, fakeAsync, TestBed, tick} from '@angular/core/testing'; +import {Component, getDebugNode} from '@angular/core'; +import {By} from '@angular/platform-browser'; +import {Directionality, BidiModule} from './index'; + +describe('Directionality', () => { + let documentElementDir, bodyDir; + + beforeAll(() => { + documentElementDir = document.documentElement.dir; + bodyDir = document.body.dir; + }); + + afterAll(() => { + document.documentElement.dir = documentElementDir; + document.body.dir = bodyDir; + }); + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [BidiModule], + declarations: [ElementWithDir, InjectsDirectionality] + }).compileComponents(); + + clearDocumentDirAttributes(); + })); + + describe('Service', () => { + it('should read dir from the html element if not specified on the body', () => { + document.documentElement.dir = 'rtl'; + + let fixture = TestBed.createComponent(InjectsDirectionality); + let testComponent = fixture.debugElement.componentInstance; + + expect(testComponent.dir.value).toBe('rtl'); + }); + + it('should read dir from the body even it is also specified on the html element', () => { + document.documentElement.dir = 'ltr'; + document.body.dir = 'rtl'; + + let fixture = TestBed.createComponent(InjectsDirectionality); + let testComponent = fixture.debugElement.componentInstance; + + expect(testComponent.dir.value).toBe('rtl'); + }); + + it('should default to ltr if nothing is specified on either body or the html element', () => { + let fixture = TestBed.createComponent(InjectsDirectionality); + let testComponent = fixture.debugElement.componentInstance; + + expect(testComponent.dir.value).toBe('ltr'); + }); + }); + + describe('Dir directive', () => { + it('should provide itself as Directionality', () => { + let fixture = TestBed.createComponent(ElementWithDir); + const injectedDirectionality = + fixture.debugElement.query(By.directive(InjectsDirectionality)).componentInstance.dir; + + fixture.detectChanges(); + + expect(injectedDirectionality.value).toBe('rtl'); + }); + + it('should emit a change event when the value changes', fakeAsync(() => { + let fixture = TestBed.createComponent(ElementWithDir); + const injectedDirectionality = + fixture.debugElement.query(By.directive(InjectsDirectionality)).componentInstance.dir; + + fixture.detectChanges(); + + expect(injectedDirectionality.value).toBe('rtl'); + expect(fixture.componentInstance.changeCount).toBe(0); + + fixture.componentInstance.direction = 'ltr'; + + fixture.detectChanges(); + tick(); + + expect(injectedDirectionality.value).toBe('ltr'); + expect(fixture.componentInstance.changeCount).toBe(1); + })); + }); +}); + + +function clearDocumentDirAttributes() { + document.documentElement.dir = ''; + document.body.dir = ''; +} + +@Component({ + template: ` +
+ +
+ ` +}) +class ElementWithDir { + direction = 'rtl'; + changeCount = 0; +} + +/** Test component with Dir directive. */ +@Component({ + selector: 'injects-directionality', + template: `
` +}) +class InjectsDirectionality { + constructor(public dir: Directionality) { + } +} diff --git a/src/lib/core/bidi/directionality.ts b/src/lib/core/bidi/directionality.ts new file mode 100644 index 000000000000..004d89ac4572 --- /dev/null +++ b/src/lib/core/bidi/directionality.ts @@ -0,0 +1,40 @@ +import { + EventEmitter, + Injectable, + Optional, + SkipSelf +} from '@angular/core'; + +export type Direction = 'ltr' | 'rtl'; + +/** + * The directionality (LTR / RTL) context for the application (or a subtree of it). + * Exposes the current direction and a stream of direction changes. + */ +@Injectable() +export class Directionality { + value: Direction = 'ltr'; + public change = new EventEmitter(); + + constructor() { + if (typeof document === 'object' && !!document) { + // TODO: handle 'auto' value - + // We still need to account for dir="auto". + // It looks like HTMLElemenet.dir is also "auto" when that's set to the attribute, + // but getComputedStyle return either "ltr" or "rtl". avoiding getComputedStyle for now + // though, we're already calling it for the theming check. + this.value = (document.body.dir || document.documentElement.dir || 'ltr') as Direction; + } + } +} + +export function DIRECTIONALITY_PROVIDER_FACTORY(parentDirectionality) { + return parentDirectionality || new Directionality(); +} + +export const DIRECTIONALITY_PROVIDER = { + // If there is already a Directionality available, use that. Otherwise, provide a new one. + provide: Directionality, + deps: [[new Optional(), new SkipSelf(), Directionality]], + useFactory: DIRECTIONALITY_PROVIDER_FACTORY +}; diff --git a/src/lib/core/bidi/index.ts b/src/lib/core/bidi/index.ts new file mode 100644 index 000000000000..428ae904ad9c --- /dev/null +++ b/src/lib/core/bidi/index.ts @@ -0,0 +1,25 @@ +import {ModuleWithProviders, NgModule} from '@angular/core'; +import {Dir} from './dir'; +import {Directionality, DIRECTIONALITY_PROVIDER} from './directionality'; + +export { + Directionality, + DIRECTIONALITY_PROVIDER, + Direction +} from './directionality'; +export {Dir} from './dir'; + +@NgModule({ + exports: [Dir], + declarations: [Dir], + providers: [Directionality] +}) +export class BidiModule { + /** @deprecated */ + static forRoot(): ModuleWithProviders { + return { + ngModule: BidiModule, + providers: [DIRECTIONALITY_PROVIDER] + }; + } +} diff --git a/src/lib/core/common-behaviors/common-module.ts b/src/lib/core/common-behaviors/common-module.ts index 9f47553ba425..ddc040caba87 100644 --- a/src/lib/core/common-behaviors/common-module.ts +++ b/src/lib/core/common-behaviors/common-module.ts @@ -1,6 +1,7 @@ import {NgModule, InjectionToken, Optional, Inject, isDevMode} from '@angular/core'; import {DOCUMENT} from '@angular/platform-browser'; import {CompatibilityModule} from '../compatibility/compatibility'; +import {BidiModule} from '../bidi/index'; /** Injection token that configures whether the Material sanity checks are enabled. */ @@ -14,8 +15,8 @@ export const MATERIAL_SANITY_CHECKS = new InjectionToken('md-sanity-che * This module should be imported to each top-level component module (e.g., MdTabsModule). */ @NgModule({ - imports: [CompatibilityModule], - exports: [CompatibilityModule], + imports: [CompatibilityModule, BidiModule], + exports: [CompatibilityModule, BidiModule], providers: [{ provide: MATERIAL_SANITY_CHECKS, useValue: true, }], diff --git a/src/lib/core/core.ts b/src/lib/core/core.ts index 8cae512882d5..e25b5eabef20 100644 --- a/src/lib/core/core.ts +++ b/src/lib/core/core.ts @@ -1,6 +1,6 @@ import {NgModule} from '@angular/core'; import {MdLineModule} from './line/line'; -import {RtlModule} from './rtl/dir'; +import {BidiModule} from './bidi/index'; import {ObserveContentModule} from './observe-content/observe-content'; import {MdOptionModule} from './option/index'; import {PortalModule} from './portal/portal-directives'; @@ -11,7 +11,7 @@ import {MdRippleModule} from './ripple/index'; // RTL -export {Dir, LayoutDirection, RtlModule} from './rtl/dir'; +export {Dir, Direction, Directionality, BidiModule} from './bidi/index'; // Mutation Observer export {ObserveContentModule, ObserveContent} from './observe-content/observe-content'; @@ -113,7 +113,7 @@ export { @NgModule({ imports: [ MdLineModule, - RtlModule, + BidiModule, MdRippleModule, ObserveContentModule, PortalModule, @@ -124,7 +124,7 @@ export { ], exports: [ MdLineModule, - RtlModule, + BidiModule, MdRippleModule, ObserveContentModule, PortalModule, diff --git a/src/lib/core/overlay/overlay-directives.spec.ts b/src/lib/core/overlay/overlay-directives.spec.ts index 8c3440865072..c333e72b22a0 100644 --- a/src/lib/core/overlay/overlay-directives.spec.ts +++ b/src/lib/core/overlay/overlay-directives.spec.ts @@ -5,7 +5,7 @@ import {ConnectedOverlayDirective, OverlayModule, OverlayOrigin} from './overlay import {OverlayContainer} from './overlay-container'; import {ConnectedPositionStrategy} from './position/connected-position-strategy'; import {ConnectedOverlayPositionChange} from './position/connected-position'; -import {Dir} from '../rtl/dir'; +import {Directionality} from '../bidi/index'; import {dispatchKeyboardEvent} from '../testing/dispatch-events'; import {ESCAPE} from '../keyboard/keycodes'; @@ -24,7 +24,7 @@ describe('Overlay directives', () => { overlayContainerElement = document.createElement('div'); return {getContainerElement: () => overlayContainerElement}; }}, - {provide: Dir, useFactory: () => { + {provide: Directionality, useFactory: () => { return dir = { value: 'ltr' }; }} ], diff --git a/src/lib/core/overlay/overlay-directives.ts b/src/lib/core/overlay/overlay-directives.ts index a7a66df277b8..6b45696a9435 100644 --- a/src/lib/core/overlay/overlay-directives.ts +++ b/src/lib/core/overlay/overlay-directives.ts @@ -23,7 +23,7 @@ import { } from './position/connected-position'; import {PortalModule} from '../portal/portal-directives'; import {ConnectedPositionStrategy} from './position/connected-position-strategy'; -import {Dir, LayoutDirection} from '../rtl/dir'; +import {Directionality, Direction} from '../bidi/index'; import {Scrollable} from './scroll/scrollable'; import {ScrollStrategy} from './scroll/scroll-strategy'; import {coerceBooleanProperty} from '../coercion/boolean-property'; @@ -157,7 +157,7 @@ export class ConnectedOverlayDirective implements OnDestroy, OnChanges { private _renderer: Renderer2, templateRef: TemplateRef, viewContainerRef: ViewContainerRef, - @Optional() private _dir: Dir) { + @Optional() private _dir: Directionality) { this._templatePortal = new TemplatePortal(templateRef, viewContainerRef); } @@ -167,7 +167,7 @@ export class ConnectedOverlayDirective implements OnDestroy, OnChanges { } /** The element's layout direction. */ - get dir(): LayoutDirection { + get dir(): Direction { return this._dir ? this._dir.value : 'ltr'; } diff --git a/src/lib/core/overlay/overlay-state.ts b/src/lib/core/overlay/overlay-state.ts index 930e4293f974..7183ce0e70f8 100644 --- a/src/lib/core/overlay/overlay-state.ts +++ b/src/lib/core/overlay/overlay-state.ts @@ -1,5 +1,5 @@ import {PositionStrategy} from './position/position-strategy'; -import {LayoutDirection} from '../rtl/dir'; +import {Direction} from '../bidi/index'; import {ScrollStrategy} from './scroll/scroll-strategy'; @@ -36,7 +36,7 @@ export class OverlayState { minHeight: number | string; /** The direction of the text in the overlay panel. */ - direction: LayoutDirection = 'ltr'; + direction: Direction = 'ltr'; // TODO(jelbourn): configuration still to add // - focus trap diff --git a/src/lib/core/rtl/dir.ts b/src/lib/core/rtl/dir.ts deleted file mode 100644 index e00680aa74a6..000000000000 --- a/src/lib/core/rtl/dir.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { - NgModule, - Directive, - HostBinding, - Output, - Input, - EventEmitter -} from '@angular/core'; - -export type LayoutDirection = 'ltr' | 'rtl'; - -/** - * Directive to listen for changes of direction of part of the DOM. - * - * Applications should use this directive instead of the native attribute so that Material - * components can listen on changes of direction. - */ -@Directive({ - selector: '[dir]', - // TODO(hansl): maybe `$implicit` isn't the best option here, but for now that's the best we got. - exportAs: '$implicit' -}) -export class Dir { - /** Layout direction of the element. */ - @Input('dir') _dir: LayoutDirection = 'ltr'; - - /** Event emitted when the direction changes. */ - @Output() dirChange = new EventEmitter(); - - /** @docs-private */ - @HostBinding('attr.dir') - get dir(): LayoutDirection { - return this._dir; - } - set dir(v: LayoutDirection) { - let old = this._dir; - this._dir = v; - if (old != this._dir) { - this.dirChange.emit(); - } - } - - /** Current layout direction of the element. */ - get value(): LayoutDirection { return this.dir; } - set value(v: LayoutDirection) { this.dir = v; } -} - - -@NgModule({ - exports: [Dir], - declarations: [Dir] -}) -export class RtlModule {} diff --git a/src/lib/datepicker/datepicker.ts b/src/lib/datepicker/datepicker.ts index d2077b0dca0c..c18578e8c989 100644 --- a/src/lib/datepicker/datepicker.ts +++ b/src/lib/datepicker/datepicker.ts @@ -17,7 +17,7 @@ import {Overlay} from '../core/overlay/overlay'; import {OverlayRef} from '../core/overlay/overlay-ref'; import {ComponentPortal} from '../core/portal/portal'; import {OverlayState} from '../core/overlay/overlay-state'; -import {Dir} from '../core/rtl/dir'; +import {Directionality} from '../core/bidi/index'; import {MdDialog} from '../dialog/dialog'; import {MdDialogRef} from '../dialog/dialog-ref'; import {PositionStrategy} from '../core/overlay/position/position-strategy'; @@ -157,7 +157,7 @@ export class MdDatepicker implements OnDestroy { private _ngZone: NgZone, private _viewContainerRef: ViewContainerRef, @Optional() private _dateAdapter: DateAdapter, - @Optional() private _dir: Dir) { + @Optional() private _dir: Directionality) { if (!this._dateAdapter) { throw createMissingDateImplError('DateAdapter'); } diff --git a/src/lib/dialog/dialog-config.ts b/src/lib/dialog/dialog-config.ts index fe00f899abdb..f608cf14856d 100644 --- a/src/lib/dialog/dialog-config.ts +++ b/src/lib/dialog/dialog-config.ts @@ -1,5 +1,5 @@ import {ViewContainerRef} from '@angular/core'; -import {LayoutDirection} from '../core'; +import {Direction} from '../core'; /** Valid ARIA roles for a dialog element. */ export type DialogRole = 'dialog' | 'alertdialog'; @@ -53,7 +53,7 @@ export class MdDialogConfig { data?: any = null; /** Layout direction for the dialog's content. */ - direction?: LayoutDirection = 'ltr'; + direction?: Direction = 'ltr'; // TODO(jelbourn): add configuration for lifecycle hooks, ARIA labelling. } diff --git a/src/lib/grid-list/grid-list.ts b/src/lib/grid-list/grid-list.ts index 4afb0f83bdc3..1dfb352965a9 100644 --- a/src/lib/grid-list/grid-list.ts +++ b/src/lib/grid-list/grid-list.ts @@ -13,7 +13,7 @@ import { import {MdGridTile} from './grid-tile'; import {TileCoordinator} from './tile-coordinator'; import {TileStyler, FitTileStyler, RatioTileStyler, FixedTileStyler} from './tile-styler'; -import {Dir} from '../core'; +import {Directionality} from '../core'; import { coerceToString, coerceToNumber, @@ -61,7 +61,7 @@ export class MdGridList implements OnInit, AfterContentChecked { constructor( private _renderer: Renderer2, private _element: ElementRef, - @Optional() private _dir: Dir) {} + @Optional() private _dir: Directionality) {} /** Amount of columns in the grid list. */ @Input() diff --git a/src/lib/menu/menu-trigger.ts b/src/lib/menu/menu-trigger.ts index a54df373ef40..3ea5381150d2 100644 --- a/src/lib/menu/menu-trigger.ts +++ b/src/lib/menu/menu-trigger.ts @@ -13,8 +13,8 @@ import {MdMenuPanel} from './menu-panel'; import {throwMdMenuMissingError} from './menu-errors'; import { isFakeMousedownFromScreenReader, - Dir, - LayoutDirection, + Directionality, + Direction, Overlay, OverlayState, OverlayRef, @@ -78,7 +78,8 @@ export class MdMenuTrigger implements AfterViewInit, OnDestroy { @Output() onMenuClose = new EventEmitter(); constructor(private _overlay: Overlay, private _element: ElementRef, - private _viewContainerRef: ViewContainerRef, @Optional() private _dir: Dir) { } + private _viewContainerRef: ViewContainerRef, + @Optional() private _dir: Directionality) { } ngAfterViewInit() { this._checkMenu(); @@ -130,7 +131,7 @@ export class MdMenuTrigger implements AfterViewInit, OnDestroy { } /** The text direction of the containing app. */ - get dir(): LayoutDirection { + get dir(): Direction { return this._dir && this._dir.value === 'rtl' ? 'rtl' : 'ltr'; } diff --git a/src/lib/menu/menu.spec.ts b/src/lib/menu/menu.spec.ts index de9cabf903c4..021d17cc63d9 100644 --- a/src/lib/menu/menu.spec.ts +++ b/src/lib/menu/menu.spec.ts @@ -18,7 +18,7 @@ import { MenuPositionY } from './index'; import {OverlayContainer} from '../core/overlay/overlay-container'; -import {Dir, LayoutDirection} from '../core/rtl/dir'; +import {Directionality, Direction} from '../core/bidi/index'; import {extendObject} from '../core/util/object-extend'; import {ESCAPE} from '../core/keyboard/keycodes'; import {dispatchKeyboardEvent} from '../core/testing/dispatch-events'; @@ -26,7 +26,7 @@ import {dispatchKeyboardEvent} from '../core/testing/dispatch-events'; describe('MdMenu', () => { let overlayContainerElement: HTMLElement; - let dir: LayoutDirection; + let dir: Direction; beforeEach(async(() => { dir = 'ltr'; @@ -44,9 +44,7 @@ describe('MdMenu', () => { document.body.style.margin = '0'; return {getContainerElement: () => overlayContainerElement}; }}, - {provide: Dir, useFactory: () => { - return {value: dir}; - }} + {provide: Directionality, useFactory: () => ({value: dir})} ] }); diff --git a/src/lib/module.ts b/src/lib/module.ts index 2041b0d2c7e5..c32d1854e330 100644 --- a/src/lib/module.ts +++ b/src/lib/module.ts @@ -4,10 +4,10 @@ import { A11yModule, MdCommonModule, MdRippleModule, + BidiModule, ObserveContentModule, OverlayModule, - PortalModule, - RtlModule + PortalModule } from './core/index'; import {MdButtonToggleModule} from './button-toggle/index'; @@ -68,7 +68,7 @@ const MATERIAL_MODULES = [ MdTooltipModule, OverlayModule, PortalModule, - RtlModule, + BidiModule, StyleModule, A11yModule, PlatformModule, diff --git a/src/lib/select/select.spec.ts b/src/lib/select/select.spec.ts index 2b7d90e41654..65e2a5a061cb 100644 --- a/src/lib/select/select.spec.ts +++ b/src/lib/select/select.spec.ts @@ -15,7 +15,7 @@ import {OverlayContainer} from '../core/overlay/overlay-container'; import {MdSelect} from './select'; import {getMdSelectDynamicMultipleError, getMdSelectNonArrayValueError} from './select-errors'; import {MdOption} from '../core/option/option'; -import {Dir} from '../core/rtl/dir'; +import {Directionality} from '../core/bidi/index'; import {DOWN_ARROW, UP_ARROW, ENTER, SPACE, HOME, END, TAB} from '../core/keyboard/keycodes'; import { ControlValueAccessor, FormControl, FormsModule, NG_VALUE_ACCESSOR, ReactiveFormsModule @@ -77,7 +77,7 @@ describe('MdSelect', () => { return {getContainerElement: () => overlayContainerElement}; }}, - {provide: Dir, useFactory: () => dir = { value: 'ltr' }}, + {provide: Directionality, useFactory: () => dir = { value: 'ltr' }}, {provide: ScrollDispatcher, useFactory: () => { return {scrolled: (_delay: number, callback: () => any) => { return scrolledSubject.asObservable().subscribe(callback); diff --git a/src/lib/select/select.ts b/src/lib/select/select.ts index 84a70f1e6a45..b1722f2e9c72 100644 --- a/src/lib/select/select.ts +++ b/src/lib/select/select.ts @@ -21,7 +21,7 @@ import { import {MdOption, MdOptionSelectionChange, MdOptgroup} from '../core/option/index'; import {ENTER, SPACE, UP_ARROW, DOWN_ARROW, HOME, END} from '../core/keyboard/keycodes'; import {FocusKeyManager} from '../core/a11y/focus-key-manager'; -import {Dir} from '../core/rtl/dir'; +import {Directionality} from '../core/bidi/index'; import {Observable} from 'rxjs/Observable'; import {Subscription} from 'rxjs/Subscription'; import {transformPlaceholder, transformPanel, fadeInContent} from './select-animations'; @@ -325,7 +325,7 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On private _changeDetectorRef: ChangeDetectorRef, renderer: Renderer2, elementRef: ElementRef, - @Optional() private _dir: Dir, + @Optional() private _dir: Directionality, @Self() @Optional() public _control: NgControl, @Attribute('tabindex') tabIndex: string, @Optional() @Inject(MD_PLACEHOLDER_GLOBAL_OPTIONS) placeholderOptions: PlaceholderOptions) { diff --git a/src/lib/sidenav/sidenav.ts b/src/lib/sidenav/sidenav.ts index f6d31da57076..fb38c3473f08 100644 --- a/src/lib/sidenav/sidenav.ts +++ b/src/lib/sidenav/sidenav.ts @@ -14,7 +14,7 @@ import { NgZone, OnDestroy, Inject, } from '@angular/core'; -import {Dir, coerceBooleanProperty} from '../core'; +import {Directionality, coerceBooleanProperty} from '../core'; import {FocusTrapFactory, FocusTrap} from '../core/a11y/focus-trap'; import {ESCAPE} from '../core/keyboard/keycodes'; import 'rxjs/add/operator/first'; @@ -357,12 +357,12 @@ export class MdSidenavContainer implements AfterContentInit { /** Whether to enable open/close trantions. */ _enableTransitions = false; - constructor(@Optional() private _dir: Dir, private _element: ElementRef, + constructor(@Optional() private _dir: Directionality, private _element: ElementRef, private _renderer: Renderer2, private _ngZone: NgZone) { // If a `Dir` directive exists up the tree, listen direction changes and update the left/right // properties to point to the proper start/end. if (_dir != null) { - _dir.dirChange.subscribe(() => this._validateDrawers()); + _dir.change.subscribe(() => this._validateDrawers()); } } diff --git a/src/lib/slider/index.ts b/src/lib/slider/index.ts index a01a1936b0f8..9632fdbcfb9f 100644 --- a/src/lib/slider/index.ts +++ b/src/lib/slider/index.ts @@ -4,11 +4,11 @@ import {CommonModule} from '@angular/common'; import {FormsModule} from '@angular/forms'; import {MdCommonModule, GestureConfig, StyleModule} from '../core'; import {MdSlider} from './slider'; -import {RtlModule} from '../core/rtl/dir'; +import {BidiModule} from '../core/bidi/index'; @NgModule({ - imports: [CommonModule, FormsModule, MdCommonModule, StyleModule, RtlModule], + imports: [CommonModule, FormsModule, MdCommonModule, StyleModule, BidiModule], exports: [MdSlider, MdCommonModule], declarations: [MdSlider], providers: [{provide: HAMMER_GESTURE_CONFIG, useClass: GestureConfig}] diff --git a/src/lib/slider/slider.spec.ts b/src/lib/slider/slider.spec.ts index 116a55687b23..43d4ba28a8eb 100644 --- a/src/lib/slider/slider.spec.ts +++ b/src/lib/slider/slider.spec.ts @@ -4,7 +4,7 @@ import {Component, DebugElement} from '@angular/core'; import {By, HAMMER_GESTURE_CONFIG} from '@angular/platform-browser'; import {MdSlider, MdSliderModule} from './index'; import {TestGestureConfig} from './test-gesture-config'; -import {RtlModule} from '../core/rtl/dir'; +import {BidiModule} from '../core/bidi/index'; import { DOWN_ARROW, END, @@ -23,7 +23,7 @@ describe('MdSlider', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - imports: [MdSliderModule, ReactiveFormsModule, FormsModule, RtlModule], + imports: [MdSliderModule, ReactiveFormsModule, FormsModule, BidiModule], declarations: [ StandardSlider, DisabledSlider, diff --git a/src/lib/slider/slider.ts b/src/lib/slider/slider.ts index d0860a840626..fc14ab03369d 100644 --- a/src/lib/slider/slider.ts +++ b/src/lib/slider/slider.ts @@ -12,7 +12,7 @@ import { } from '@angular/core'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; import {coerceBooleanProperty, coerceNumberProperty, HammerInput} from '../core'; -import {Dir} from '../core/rtl/dir'; +import {Directionality} from '../core/bidi/index'; import { DOWN_ARROW, END, @@ -382,7 +382,8 @@ export class MdSlider extends _MdSliderMixinBase } constructor(renderer: Renderer2, private _elementRef: ElementRef, - private _focusOriginMonitor: FocusOriginMonitor, @Optional() private _dir: Dir) { + private _focusOriginMonitor: FocusOriginMonitor, + @Optional() private _dir: Directionality) { super(); this._focusOriginMonitor.monitor(this._elementRef.nativeElement, renderer, true) .subscribe((origin: FocusOrigin) => this._isActive = !!origin && origin !== 'keyboard'); diff --git a/src/lib/snack-bar/snack-bar-config.ts b/src/lib/snack-bar/snack-bar-config.ts index 031c9c3af9e1..b593134f1bd3 100644 --- a/src/lib/snack-bar/snack-bar-config.ts +++ b/src/lib/snack-bar/snack-bar-config.ts @@ -1,5 +1,5 @@ import {ViewContainerRef} from '@angular/core'; -import {AriaLivePoliteness, LayoutDirection} from '../core'; +import {AriaLivePoliteness, Direction} from '../core'; /** * Configuration used when opening a snack-bar. @@ -21,5 +21,5 @@ export class MdSnackBarConfig { extraClasses?: string[]; /** Text layout direction for the snack bar. */ - direction?: LayoutDirection = 'ltr'; + direction?: Direction = 'ltr'; } diff --git a/src/lib/tabs/tab-body.spec.ts b/src/lib/tabs/tab-body.spec.ts index 880b32470882..9c0f7e0023b2 100644 --- a/src/lib/tabs/tab-body.spec.ts +++ b/src/lib/tabs/tab-body.spec.ts @@ -1,7 +1,7 @@ import {async, ComponentFixture, TestBed, flushMicrotasks, fakeAsync} from '@angular/core/testing'; import {Component, ViewChild, TemplateRef, ViewContainerRef} from '@angular/core'; import {NoopAnimationsModule} from '@angular/platform-browser/animations'; -import {LayoutDirection, Dir} from '../core/rtl/dir'; +import {Direction, Directionality} from '../core/bidi/index'; import {TemplatePortal} from '../core/portal/portal'; import {MdTabBody} from './tab-body'; import {MdRippleModule} from '../core/ripple/index'; @@ -10,7 +10,7 @@ import {PortalModule} from '../core'; describe('MdTabBody', () => { - let dir: LayoutDirection = 'ltr'; + let dir: Direction = 'ltr'; beforeEach(async(() => { dir = 'ltr'; @@ -21,8 +21,8 @@ describe('MdTabBody', () => { SimpleTabBodyApp, ], providers: [ - { provide: Dir, useFactory: () => { return {value: dir}; } - }] + {provide: Directionality, useFactory: () => ({value: dir})} + ] }); TestBed.compileComponents(); diff --git a/src/lib/tabs/tab-body.ts b/src/lib/tabs/tab-body.ts index 77e78a2baa75..182c293d6f59 100644 --- a/src/lib/tabs/tab-body.ts +++ b/src/lib/tabs/tab-body.ts @@ -18,7 +18,7 @@ import { transition, AnimationEvent, } from '@angular/animations'; -import {TemplatePortal, PortalHostDirective, Dir, LayoutDirection} from '../core'; +import {TemplatePortal, PortalHostDirective, Directionality, Direction} from '../core'; import 'rxjs/add/operator/map'; /** @@ -116,7 +116,8 @@ export class MdTabBody implements OnInit, AfterViewChecked { } } - constructor(@Optional() private _dir: Dir, private _elementRef: ElementRef) { } + constructor(private _elementRef: ElementRef, + @Optional() private _dir: Directionality) { } /** * After initialized, check if the content is centered and has an origin. If so, set the @@ -157,7 +158,7 @@ export class MdTabBody implements OnInit, AfterViewChecked { } /** The text direction of the containing app. */ - _getLayoutDirection(): LayoutDirection { + _getLayoutDirection(): Direction { return this._dir && this._dir.value === 'rtl' ? 'rtl' : 'ltr'; } diff --git a/src/lib/tabs/tab-header.spec.ts b/src/lib/tabs/tab-header.spec.ts index 308ab011b242..731b6c4ccade 100644 --- a/src/lib/tabs/tab-header.spec.ts +++ b/src/lib/tabs/tab-header.spec.ts @@ -1,6 +1,6 @@ import {async, ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing'; import {Component, ViewChild, ViewContainerRef} from '@angular/core'; -import {LayoutDirection, Dir} from '../core/rtl/dir'; +import {Direction, Directionality} from '../core/bidi/index'; import {MdTabHeader} from './tab-header'; import {MdRippleModule} from '../core/ripple/index'; import {CommonModule} from '@angular/common'; @@ -17,8 +17,8 @@ import {By} from '@angular/platform-browser'; describe('MdTabHeader', () => { - let dir: LayoutDirection = 'ltr'; - let dirChange = new Subject(); + let dir: Direction = 'ltr'; + let change = new Subject(); let fixture: ComponentFixture; let appComponent: SimpleTabHeaderApp; @@ -33,9 +33,7 @@ describe('MdTabHeader', () => { SimpleTabHeaderApp, ], providers: [ - {provide: Dir, useFactory: () => { - return {value: dir, dirChange: dirChange.asObservable()}; - }}, + {provide: Directionality, useFactory: () => ({value: dir, change: change.asObservable()})}, {provide: ViewportRuler, useClass: FakeViewportRuler}, ] }); @@ -239,13 +237,13 @@ describe('MdTabHeader', () => { it('should re-align the ink bar when the direction changes', () => { fixture = TestBed.createComponent(SimpleTabHeaderApp); - fixture.detectChanges(); const inkBar = fixture.componentInstance.mdTabHeader._inkBar; - spyOn(inkBar, 'alignToElement'); - dirChange.next(); + fixture.detectChanges(); + + change.next(); fixture.detectChanges(); expect(inkBar.alignToElement).toHaveBeenCalled(); @@ -301,7 +299,7 @@ class SimpleTabHeaderApp { focusedIndex: number; disabledTabIndex = 1; tabs: Tab[] = [{label: 'tab one'}, {label: 'tab one'}, {label: 'tab one'}, {label: 'tab one'}]; - dir: LayoutDirection = 'ltr'; + dir: Direction = 'ltr'; @ViewChild(MdTabHeader) mdTabHeader: MdTabHeader; diff --git a/src/lib/tabs/tab-header.ts b/src/lib/tabs/tab-header.ts index b607266aad9b..e9d8365a3be1 100644 --- a/src/lib/tabs/tab-header.ts +++ b/src/lib/tabs/tab-header.ts @@ -14,7 +14,14 @@ import { OnDestroy, NgZone, } from '@angular/core'; -import {RIGHT_ARROW, LEFT_ARROW, ENTER, Dir, LayoutDirection, coerceBooleanProperty} from '../core'; +import { + RIGHT_ARROW, + LEFT_ARROW, + ENTER, + Directionality, + Direction, + coerceBooleanProperty +} from '../core'; import {MdTabLabelWrapper} from './tab-label-wrapper'; import {MdInkBar} from './ink-bar'; import {Subscription} from 'rxjs/Subscription'; @@ -123,7 +130,7 @@ export class MdTabHeader implements AfterContentChecked, AfterContentInit, OnDes constructor( private _elementRef: ElementRef, private _ngZone: NgZone, - @Optional() private _dir: Dir) { } + @Optional() private _dir: Directionality) { } ngAfterContentChecked(): void { // If the number of tab labels have changed, check if scrolling should be enabled @@ -168,7 +175,7 @@ export class MdTabHeader implements AfterContentChecked, AfterContentInit, OnDes */ ngAfterContentInit() { this._realignInkBar = this._ngZone.runOutsideAngular(() => { - let dirChange = this._dir ? this._dir.dirChange : Observable.of(null); + let dirChange = this._dir ? this._dir.change : Observable.of(null); let resize = typeof window !== 'undefined' ? Observable.fromEvent(window, 'resize').auditTime(10) : Observable.of(null); @@ -280,7 +287,7 @@ export class MdTabHeader implements AfterContentChecked, AfterContentInit, OnDes } /** The layout direction of the containing app. */ - _getLayoutDirection(): LayoutDirection { + _getLayoutDirection(): Direction { return this._dir && this._dir.value === 'rtl' ? 'rtl' : 'ltr'; } diff --git a/src/lib/tabs/tab-nav-bar/tab-nav-bar.spec.ts b/src/lib/tabs/tab-nav-bar/tab-nav-bar.spec.ts index 275d2b6df663..0616468bca36 100644 --- a/src/lib/tabs/tab-nav-bar/tab-nav-bar.spec.ts +++ b/src/lib/tabs/tab-nav-bar/tab-nav-bar.spec.ts @@ -6,12 +6,12 @@ import {By} from '@angular/platform-browser'; import {ViewportRuler} from '../../core/overlay/position/viewport-ruler'; import {FakeViewportRuler} from '../../core/overlay/position/fake-viewport-ruler'; import {dispatchFakeEvent, dispatchMouseEvent} from '../../core/testing/dispatch-events'; -import {Dir, LayoutDirection} from '../../core/rtl/dir'; +import {Direction, Directionality} from '../../core/bidi/index'; import {Subject} from 'rxjs/Subject'; describe('MdTabNavBar', () => { - let dir: LayoutDirection = 'ltr'; + let dir: Direction = 'ltr'; let dirChange = new Subject(); beforeEach(async(() => { @@ -22,9 +22,10 @@ describe('MdTabNavBar', () => { TabLinkWithNgIf, ], providers: [ - {provide: Dir, useFactory: () => { - return {value: dir, dirChange: dirChange.asObservable()}; - }}, + {provide: Directionality, useFactory: () => ({ + value: dir, + change: dirChange.asObservable() + })}, {provide: ViewportRuler, useClass: FakeViewportRuler}, ] }); diff --git a/src/lib/tabs/tab-nav-bar/tab-nav-bar.ts b/src/lib/tabs/tab-nav-bar/tab-nav-bar.ts index 2ec833080d67..83883d146656 100644 --- a/src/lib/tabs/tab-nav-bar/tab-nav-bar.ts +++ b/src/lib/tabs/tab-nav-bar/tab-nav-bar.ts @@ -14,7 +14,7 @@ import { import {MdInkBar} from '../ink-bar'; import {MdRipple} from '../../core/ripple/index'; import {ViewportRuler} from '../../core/overlay/position/viewport-ruler'; -import {Dir, MD_RIPPLE_GLOBAL_OPTIONS, Platform, RippleGlobalOptions} from '../../core'; +import {Directionality, MD_RIPPLE_GLOBAL_OPTIONS, Platform, RippleGlobalOptions} from '../../core'; import {Observable} from 'rxjs/Observable'; import 'rxjs/add/operator/auditTime'; import 'rxjs/add/operator/takeUntil'; @@ -43,7 +43,7 @@ export class MdTabNavBar implements AfterContentInit, OnDestroy { @ViewChild(MdInkBar) _inkBar: MdInkBar; - constructor(@Optional() private _dir: Dir, private _ngZone: NgZone) { } + constructor(@Optional() private _dir: Directionality, private _ngZone: NgZone) { } /** Notifies the component that the active link has been changed. */ updateActiveLink(element: ElementRef) { @@ -53,7 +53,7 @@ export class MdTabNavBar implements AfterContentInit, OnDestroy { ngAfterContentInit(): void { this._ngZone.runOutsideAngular(() => { - let dirChange = this._dir ? this._dir.dirChange : Observable.of(null); + let dirChange = this._dir ? this._dir.change : Observable.of(null); let resize = typeof window !== 'undefined' ? Observable.fromEvent(window, 'resize').auditTime(10) : Observable.of(null); diff --git a/src/lib/tooltip/tooltip.spec.ts b/src/lib/tooltip/tooltip.spec.ts index 8e5dc8a3a333..78255f4c4799 100644 --- a/src/lib/tooltip/tooltip.spec.ts +++ b/src/lib/tooltip/tooltip.spec.ts @@ -17,7 +17,7 @@ import {By} from '@angular/platform-browser'; import {NoopAnimationsModule} from '@angular/platform-browser/animations'; import {TooltipPosition, MdTooltip, MdTooltipModule, SCROLL_THROTTLE_MS} from './index'; import {OverlayContainer} from '../core'; -import {Dir, LayoutDirection} from '../core/rtl/dir'; +import {Directionality, Direction} from '../core/bidi/index'; import {OverlayModule} from '../core/overlay/overlay-directives'; import {Platform} from '../core/platform/platform'; import {Scrollable} from '../core/overlay/scroll/scrollable'; @@ -28,7 +28,7 @@ const initialTooltipMessage = 'initial tooltip message'; describe('MdTooltip', () => { let overlayContainerElement: HTMLElement; - let dir: {value: LayoutDirection}; + let dir: {value: Direction}; beforeEach(async(() => { TestBed.configureTestingModule({ @@ -41,7 +41,7 @@ describe('MdTooltip', () => { document.body.appendChild(overlayContainerElement); return {getContainerElement: () => overlayContainerElement}; }}, - {provide: Dir, useFactory: () => { + {provide: Directionality, useFactory: () => { return dir = { value: 'ltr' }; }} ] diff --git a/src/lib/tooltip/tooltip.ts b/src/lib/tooltip/tooltip.ts index 2274f5279023..cd25dca08227 100644 --- a/src/lib/tooltip/tooltip.ts +++ b/src/lib/tooltip/tooltip.ts @@ -28,7 +28,7 @@ import { } from '../core'; import {Observable} from 'rxjs/Observable'; import {Subject} from 'rxjs/Subject'; -import {Dir} from '../core/rtl/dir'; +import {Directionality} from '../core/bidi/index'; import {Platform} from '../core/platform/index'; import 'rxjs/add/operator/first'; import {ScrollDispatcher} from '../core/overlay/scroll/scroll-dispatcher'; @@ -155,7 +155,7 @@ export class MdTooltip implements OnDestroy { private _ngZone: NgZone, private _renderer: Renderer2, private _platform: Platform, - @Optional() private _dir: Dir) { + @Optional() private _dir: Directionality) { // The mouse events shouldn't be bound on iOS devices, because // they can prevent the first tap from firing its click event. @@ -366,7 +366,8 @@ export class TooltipComponent { /** Subject for notifying that the tooltip has been hidden from the view */ private _onHide: Subject = new Subject(); - constructor(@Optional() private _dir: Dir, private _changeDetectorRef: ChangeDetectorRef) {} + constructor(@Optional() private _dir: Directionality, + private _changeDetectorRef: ChangeDetectorRef) {} /** * Shows the tooltip with an animation originating from the provided origin