Skip to content

Commit

Permalink
feat(bidi): added bidi to cdk
Browse files Browse the repository at this point in the history
  • Loading branch information
pimenovoleg committed May 16, 2018
1 parent 611413d commit 89b6d23
Show file tree
Hide file tree
Showing 16 changed files with 315 additions and 34 deletions.
6 changes: 0 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
"@types/chalk": "^2.2.0",
"@types/fs-extra": "^5.0.2",
"@types/glob": "^5.0.35",
"@types/hammerjs": "2.0.35",
"@types/jasmine": "^2.8.7",
"@types/node": "9.4.6",
"@types/rx": "4.1.1",
Expand Down
11 changes: 11 additions & 0 deletions src/cdk/bidi/bidi-module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { NgModule } from '@angular/core';

import { Dir } from './dir';


@NgModule({
exports: [Dir],
declarations: [Dir]
})
export class BidiModule {
}
28 changes: 28 additions & 0 deletions src/cdk/bidi/dir-document-token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { DOCUMENT } from '@angular/common';
import { inject, InjectionToken } from '@angular/core';


/**
* 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.
*
* This token is defined in a separate file from Directionality as a workaround for
* https://github.com/angular/angular/issues/22559
*
* @docs-private
*/
export const DIR_DOCUMENT = new InjectionToken<Document>('cdk-dir-doc', {
providedIn: 'root',
factory: DIR_DOCUMENT_FACTORY
});

/** @docs-private */
export function DIR_DOCUMENT_FACTORY(): Document {
return inject(DOCUMENT);
}
63 changes: 63 additions & 0 deletions src/cdk/bidi/dir.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {
Directive,
Output,
Input,
EventEmitter,
AfterContentInit,
OnDestroy
} from '@angular/core';

import { Direction, Directionality } from './directionality';


/**
* Directive to listen for changes of direction of part of the DOM.
*
* Provides itself as Directionality such that descendant directives only need to ever inject
* Directionality to get the closest direction.
*/
@Directive({
selector: '[dir]',
providers: [{provide: Directionality, useExisting: Dir}],
host: {'[dir]': 'dir'},
exportAs: 'dir'
})
export class Dir implements Directionality, AfterContentInit, OnDestroy {
_dir: Direction = 'ltr';

/** Event emitted when the direction changes. */
@Output('dirChange') change = new EventEmitter<Direction>();

/** @docs-private */
@Input()
get dir(): Direction {
return this._dir;
}

set dir(v: Direction) {
const old = this._dir;
this._dir = v;

if (old !== this._dir && this._isInitialized) {
this.change.emit(this._dir);
}
}

/** Current layout direction of the element. */
get value(): Direction {
return this.dir;
}

/** Whether the `value` has been set to its initial value. */
private _isInitialized: boolean = false;

/** Initialize once default value has been set. */
ngAfterContentInit() {
this._isInitialized = true;
}

ngOnDestroy() {
this.change.complete();
}
}

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

import {BidiModule, Directionality, Direction, DIR_DOCUMENT} from './index';


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

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';

const fixture = TestBed.createComponent(InjectsDirectionality);
const 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';

const fixture = TestBed.createComponent(InjectsDirectionality);
const 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', () => {
const fixture = TestBed.createComponent(InjectsDirectionality);
const testComponent = fixture.debugElement.componentInstance;

expect(testComponent.dir.value).toBe('ltr');
});

it('should complete the `change` stream on destroy', () => {
const fixture = TestBed.createComponent(InjectsDirectionality);
const spy = jasmine.createSpy('complete spy');
const subscription =
fixture.componentInstance.dir.change.subscribe(undefined, undefined, spy);

fixture.componentInstance.dir.ngOnDestroy();
expect(spy).toHaveBeenCalled();

subscription.unsubscribe();
});

});

describe('Dir directive', () => {
it('should provide itself as Directionality', () => {
const 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(() => {
const fixture = TestBed.createComponent(ElementWithDir);
const injectedDirectionality =
fixture.debugElement.query(By.directive(InjectsDirectionality)).componentInstance.dir;

fixture.detectChanges();

let direction = injectedDirectionality.value;
injectedDirectionality.change.subscribe((dir: Direction) => {
direction = dir;
});

expect(direction).toBe('rtl');
expect(injectedDirectionality.value).toBe('rtl');
expect(fixture.componentInstance.changeCount).toBe(0);

fixture.componentInstance.direction = 'ltr';

fixture.detectChanges();

expect(direction).toBe('ltr');
expect(injectedDirectionality.value).toBe('ltr');
expect(fixture.componentInstance.changeCount).toBe(1);
}));

it('should complete the change stream on destroy', fakeAsync(() => {
const fixture = TestBed.createComponent(ElementWithDir);
const dir =
fixture.debugElement.query(By.directive(InjectsDirectionality)).componentInstance.dir;
const spy = jasmine.createSpy('complete spy');
const subscription = dir.change.subscribe(undefined, undefined, spy);

fixture.destroy();
expect(spy).toHaveBeenCalled();
subscription.unsubscribe();
}));

});
});


@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 IFakeDocument {
documentElement: { dir?: string };
body: { dir?: string };
}
36 changes: 36 additions & 0 deletions src/cdk/bidi/directionality.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { EventEmitter, Inject, Injectable, Optional, OnDestroy } from '@angular/core';

import {DIR_DOCUMENT} from './dir-document-token';


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({providedIn: 'root'})
export class Directionality implements OnDestroy {
/** The current 'ltr' or 'rtl' value. */
readonly value: Direction = 'ltr';

/** Stream that emits whenever the 'ltr' / 'rtl' state changes. */
readonly change = new EventEmitter<Direction>();

constructor(@Optional() @Inject(DIR_DOCUMENT) _document?: any) {
if (_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
const bodyDir = _document.body ? _document.body.dir : null;
const htmlDir = _document.documentElement ? _document.documentElement.dir : null;
this.value = (bodyDir || htmlDir || 'ltr') as Direction;
}
}

ngOnDestroy() {
this.change.complete();
}
}
2 changes: 2 additions & 0 deletions src/cdk/bidi/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

export * from './public-api';
7 changes: 7 additions & 0 deletions src/cdk/bidi/public-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

export { Directionality, Direction } from './directionality';
export { DIR_DOCUMENT } from './dir-document-token';
export { Dir } from './dir';

export * from './bidi-module';

13 changes: 13 additions & 0 deletions src/cdk/bidi/tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"extends": "../tsconfig.build",
"files": [
"public-api.ts"
],
"angularCompilerOptions": {
"strictMetadataEmit": true,
"flatModuleOutFile": "index.js",
"flatModuleId": "@ptsecurity/cdk/bidi",
"skipTemplateCodegen": true,
"fullTemplateTypeCheck": true
}
}
32 changes: 10 additions & 22 deletions src/lib/core/common-behaviors/common-module.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { NgModule, InjectionToken, Optional, Inject, isDevMode } from '@angular/core';
import { BidiModule } from '@ptsecurity/cdk/bidi';


// Injection token that configures whether the Mosaic sanity checks are enabled.
export const MС_SANITY_CHECKS = new InjectionToken<boolean>('mc-sanity-checks');
export const MС_SANITY_CHECKS = new InjectionToken<boolean>('mc-sanity-checks', {
providedIn: 'root',
factory: MC_SANITY_CHECKS_FACTORY
});

export function MC_SANITY_CHECKS_FACTORY(): boolean {
return true;
}

/**
* Module that captures anything that should be loaded and/or run for *all* Mosaic
Expand All @@ -12,19 +19,13 @@ export const MС_SANITY_CHECKS = new InjectionToken<boolean>('mc-sanity-checks')
* This module should be imported to each top-level component module (e.g., MatTabsModule).
*/
@NgModule({
imports: [],
exports: [],
providers: [{
provide: MС_SANITY_CHECKS, useValue: true
}]
imports: [ BidiModule ],
exports: [ BidiModule ]
})
export class McCommonModule {
// Whether we've done the global sanity checks (e.g. a theme is loaded, there is a doctype).
private _hasDoneGlobalChecks = false;

// Whether we've already checked for HammerJs availability.
private _hasCheckedHammer = false;

// Reference to the global `document` object.
private _document = typeof document === 'object' && document ? document : null;

Expand Down Expand Up @@ -81,17 +82,4 @@ export class McCommonModule {
this._document.body.removeChild(testElement);
}
}

// Checks whether HammerJS is available.
_checkHammerIsAvailable(): void {
if (this._hasCheckedHammer || !this._window) {
return;
}

if (this._areChecksEnabled() && !this._window['Hammer']) {
console.warn(
'Could not find HammerJS. Certain Mosaic components may not work correctly.');
}
this._hasCheckedHammer = true;
}
}
1 change: 0 additions & 1 deletion src/lib/divider/README.md
Original file line number Diff line number Diff line change
@@ -1 +0,0 @@
Please see the official documentation at https://material.angular.io/components/component/divider
1 change: 1 addition & 0 deletions tests/karma-test-shim.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ System.config({

'@ptsecurity/cdk': 'dist/packages/cdk/index.js',
'@ptsecurity/cdk/a11y': 'dist/packages/cdk/a11y/index.js',
'@ptsecurity/cdk/bidi': 'dist/packages/cdk/bidi/index.js',
'@ptsecurity/cdk/collections': 'dist/packages/cdk/collections/index.js',
'@ptsecurity/cdk/keycodes': 'dist/packages/cdk/keycodes/index.js',
'@ptsecurity/cdk/platform': 'dist/packages/cdk/platform/index.js',
Expand Down
Loading

0 comments on commit 89b6d23

Please sign in to comment.