Skip to content

feat(directionality): a provider to get the overall directionality #4044

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 19, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/lib/autocomplete/autocomplete-trigger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,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';
Expand Down Expand Up @@ -120,8 +120,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) {}

Expand Down
6 changes: 3 additions & 3 deletions src/lib/autocomplete/autocomplete.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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(() => {
Expand Down Expand Up @@ -70,7 +70,7 @@ describe('MdAutocomplete', () => {

return {getContainerElement: () => overlayContainerElement};
}},
{provide: Dir, useFactory: () => ({value: dir})},
{provide: Directionality, useFactory: () => ({value: dir})},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than changing all of these tests to add the Directionality, we now have a MdCommonModule that all of the components import. You can add BidiModule to the imports of the common module.

{provide: ScrollDispatcher, useFactory: () => {
return {scrolled: (_delay: number, callback: () => any) => {
return scrolledSubject.asObservable().subscribe(callback);
Expand Down
66 changes: 66 additions & 0 deletions src/lib/core/bidi/dir.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

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<void>();

/** @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;
}
}

104 changes: 104 additions & 0 deletions src/lib/core/bidi/directionality.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import {async, fakeAsync, TestBed, tick} from '@angular/core/testing';
import {Component} from '@angular/core';
import {By} from '@angular/platform-browser';
import {BidiModule, Directionality, DIR_DOCUMENT} from './index';

describe('Directionality', () => {
let fakeDocument: FakeDocument;

beforeEach(async(() => {
fakeDocument = {body: {}, documentElement: {}};

TestBed.configureTestingModule({
imports: [BidiModule],
declarations: [ElementWithDir, InjectsDirectionality],
providers: [{provide: DIR_DOCUMENT, useFactory: () => fakeDocument}],
}).compileComponents();
}));

describe('Service', () => {
it('should read dir from the html element if not specified on the body', () => {
fakeDocument.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', () => {
fakeDocument.documentElement.dir = 'ltr';
fakeDocument.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);
}));
});
});


@Component({
template: `
<div [dir]="direction" (dirChange)="changeCount= changeCount + 1">
<injects-directionality></injects-directionality>
</div>
`
})
class ElementWithDir {
direction = 'rtl';
changeCount = 0;
}

/** Test component with Dir directive. */
@Component({
selector: 'injects-directionality',
template: `<div></div>`
})
class InjectsDirectionality {
constructor(public dir: Directionality) { }
}

interface FakeDocument {
documentElement: {dir?: string};
body: {dir?: string};
}
64 changes: 64 additions & 0 deletions src/lib/core/bidi/directionality.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {
EventEmitter,
Injectable,
Optional,
SkipSelf,
Inject,
InjectionToken,
} from '@angular/core';
import {DOCUMENT} from '@angular/platform-browser';


export type Direction = 'ltr' | 'rtl';

/**
* Injection token used to inject the document into Directionality.
* This is used so that the value can be faked in tests.
*
* We can't use the real document in tests because changing the real `dir` causes geometry-based
* tests in Safari to fail.
*
* We also can't re-provide the DOCUMENT token from platform-brower because the unit tests
* themselves use things like `querySelector` in test code.
*/
export const DIR_DOCUMENT = new InjectionToken<Document>('md-dir-doc');

/**
* 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';
change = new EventEmitter<void>();

constructor(@Optional() @Inject(DIR_DOCUMENT) _document?: any) {
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, _document) {
return parentDirectionality || new Directionality(_document);
}

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], [new Optional(), DOCUMENT]],
useFactory: DIRECTIONALITY_PROVIDER_FACTORY
};
31 changes: 31 additions & 0 deletions src/lib/core/bidi/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {NgModule} from '@angular/core';
import {DOCUMENT} from '@angular/platform-browser';
import {Dir} from './dir';
import {DIR_DOCUMENT, Directionality, DIRECTIONALITY_PROVIDER} from './directionality';


export {
Directionality,
DIRECTIONALITY_PROVIDER,
DIR_DOCUMENT,
Direction,
} from './directionality';
export {Dir} from './dir';

@NgModule({
exports: [Dir],
declarations: [Dir],
providers: [
{provide: DIR_DOCUMENT, useExisting: DOCUMENT},
Directionality,
]
})
export class BidiModule { }
5 changes: 3 additions & 2 deletions src/lib/core/common-behaviors/common-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,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. */
Expand All @@ -22,8 +23,8 @@ export const MATERIAL_SANITY_CHECKS = new InjectionToken<boolean>('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,
}],
Expand Down
8 changes: 4 additions & 4 deletions src/lib/core/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

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';
Expand All @@ -19,7 +19,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';
Expand Down Expand Up @@ -121,7 +121,7 @@ export {
@NgModule({
imports: [
MdLineModule,
RtlModule,
BidiModule,
MdRippleModule,
ObserveContentModule,
PortalModule,
Expand All @@ -132,7 +132,7 @@ export {
],
exports: [
MdLineModule,
RtlModule,
BidiModule,
MdRippleModule,
ObserveContentModule,
PortalModule,
Expand Down
4 changes: 2 additions & 2 deletions src/lib/core/overlay/overlay-directives.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -24,7 +24,7 @@ describe('Overlay directives', () => {
overlayContainerElement = document.createElement('div');
return {getContainerElement: () => overlayContainerElement};
}},
{provide: Dir, useFactory: () => {
{provide: Directionality, useFactory: () => {
return dir = { value: 'ltr' };
}}
],
Expand Down
Loading