`. `TemplatePortalDirectives` *is* a `Portal`.
+
+Usage:
+```html
+
+ The content of this template is captured by the portal.
+
+
+
+
+
+
+ The content of this template is captured by the portal.
+
+```
+
+A component can use `@ViewChild` or `@ViewChildren` to get a reference to a
+`TemplatePortalDirective`.
+
+##### `ComponentPortal`
+Used to create a portal from a component type.
+
+Usage:
+```ts
+this.userSettingsPortal = new ComponentPortal(UserSettingsComponent);
+```
+
+
+##### `PortalHostDirective`
+Used to add a portal host to a template. `PortalHostDirective` *is* a `PortalHost`.
+
+Usage:
+```html
+
+
+```
diff --git a/src/cdk/portal/dom-portal-host.ts b/src/cdk/portal/dom-portal-host.ts
new file mode 100644
index 000000000000..dc64fdc88bc2
--- /dev/null
+++ b/src/cdk/portal/dom-portal-host.ts
@@ -0,0 +1,107 @@
+/**
+ * @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 {
+ ComponentFactoryResolver,
+ ComponentRef,
+ EmbeddedViewRef,
+ ApplicationRef,
+ Injector,
+} from '@angular/core';
+import {BasePortalHost, ComponentPortal, TemplatePortal} from './portal';
+
+
+/**
+ * A PortalHost for attaching portals to an arbitrary DOM element outside of the Angular
+ * application context.
+ *
+ * This is the only part of the portal core that directly touches the DOM.
+ */
+export class DomPortalHost extends BasePortalHost {
+ constructor(
+ private _hostDomElement: Element,
+ private _componentFactoryResolver: ComponentFactoryResolver,
+ private _appRef: ApplicationRef,
+ private _defaultInjector: Injector) {
+ super();
+ }
+
+ /**
+ * Attach the given ComponentPortal to DOM element using the ComponentFactoryResolver.
+ * @param portal Portal to be attached
+ */
+ attachComponentPortal(portal: ComponentPortal): ComponentRef {
+ let componentFactory = this._componentFactoryResolver.resolveComponentFactory(portal.component);
+ let componentRef: ComponentRef;
+
+ // If the portal specifies a ViewContainerRef, we will use that as the attachment point
+ // for the component (in terms of Angular's component tree, not rendering).
+ // When the ViewContainerRef is missing, we use the factory to create the component directly
+ // and then manually attach the view to the application.
+ if (portal.viewContainerRef) {
+ componentRef = portal.viewContainerRef.createComponent(
+ componentFactory,
+ portal.viewContainerRef.length,
+ portal.injector || portal.viewContainerRef.parentInjector);
+
+ this.setDisposeFn(() => componentRef.destroy());
+ } else {
+ componentRef = componentFactory.create(portal.injector || this._defaultInjector);
+ this._appRef.attachView(componentRef.hostView);
+ this.setDisposeFn(() => {
+ this._appRef.detachView(componentRef.hostView);
+ componentRef.destroy();
+ });
+ }
+ // At this point the component has been instantiated, so we move it to the location in the DOM
+ // where we want it to be rendered.
+ this._hostDomElement.appendChild(this._getComponentRootNode(componentRef));
+
+ return componentRef;
+ }
+
+ /**
+ * Attaches a template portal to the DOM as an embedded view.
+ * @param portal Portal to be attached.
+ */
+ attachTemplatePortal(portal: TemplatePortal): Map {
+ let viewContainer = portal.viewContainerRef;
+ let viewRef = viewContainer.createEmbeddedView(portal.templateRef);
+ viewRef.detectChanges();
+
+ // The method `createEmbeddedView` will add the view as a child of the viewContainer.
+ // But for the DomPortalHost the view can be added everywhere in the DOM (e.g Overlay Container)
+ // To move the view to the specified host element. We just re-append the existing root nodes.
+ viewRef.rootNodes.forEach(rootNode => this._hostDomElement.appendChild(rootNode));
+
+ this.setDisposeFn((() => {
+ let index = viewContainer.indexOf(viewRef);
+ if (index !== -1) {
+ viewContainer.remove(index);
+ }
+ }));
+
+ // TODO(jelbourn): Return locals from view.
+ return new Map();
+ }
+
+ /**
+ * Clears out a portal from the DOM.
+ */
+ dispose(): void {
+ super.dispose();
+ if (this._hostDomElement.parentNode != null) {
+ this._hostDomElement.parentNode.removeChild(this._hostDomElement);
+ }
+ }
+
+ /** Gets the root HTMLElement for an instantiated component. */
+ private _getComponentRootNode(componentRef: ComponentRef): HTMLElement {
+ return (componentRef.hostView as EmbeddedViewRef).rootNodes[0] as HTMLElement;
+ }
+}
diff --git a/src/cdk/portal/index.ts b/src/cdk/portal/index.ts
new file mode 100644
index 000000000000..3006ee0af57d
--- /dev/null
+++ b/src/cdk/portal/index.ts
@@ -0,0 +1,11 @@
+/**
+ * @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
+ */
+
+export * from './portal';
+export * from './dom-portal-host';
+export * from './portal-directives';
diff --git a/src/cdk/portal/portal-directives.ts b/src/cdk/portal/portal-directives.ts
new file mode 100644
index 000000000000..15ef6a675332
--- /dev/null
+++ b/src/cdk/portal/portal-directives.ts
@@ -0,0 +1,138 @@
+/**
+ * @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,
+ ComponentRef,
+ Directive,
+ TemplateRef,
+ ComponentFactoryResolver,
+ ViewContainerRef,
+ OnDestroy,
+ Input,
+} from '@angular/core';
+import {Portal, TemplatePortal, ComponentPortal, BasePortalHost} from './portal';
+
+
+/**
+ * Directive version of a `TemplatePortal`. Because the directive *is* a TemplatePortal,
+ * the directive instance itself can be attached to a host, enabling declarative use of portals.
+ *
+ * Usage:
+ *
+ * Hello {{name}}
+ *
+ */
+@Directive({
+ selector: '[cdk-portal], [cdkPortal], [portal]',
+ exportAs: 'cdkPortal',
+})
+export class TemplatePortalDirective extends TemplatePortal {
+ constructor(templateRef: TemplateRef, viewContainerRef: ViewContainerRef) {
+ super(templateRef, viewContainerRef);
+ }
+}
+
+
+/**
+ * Directive version of a PortalHost. Because the directive *is* a PortalHost, portals can be
+ * directly attached to it, enabling declarative use.
+ *
+ * Usage:
+ *
+ */
+@Directive({
+ selector: '[cdkPortalHost], [portalHost]',
+ inputs: ['portal: cdkPortalHost']
+})
+export class PortalHostDirective extends BasePortalHost implements OnDestroy {
+ /** The attached portal. */
+ private _portal: Portal | null = null;
+
+ constructor(
+ private _componentFactoryResolver: ComponentFactoryResolver,
+ private _viewContainerRef: ViewContainerRef) {
+ super();
+ }
+
+ /** @deprecated */
+ @Input('portalHost')
+ get _deprecatedPortal() { return this.portal; }
+ set _deprecatedPortal(v) { this.portal = v; }
+
+ /** Portal associated with the Portal host. */
+ get portal(): Portal | null {
+ return this._portal;
+ }
+
+ set portal(portal: Portal | null) {
+ if (this.hasAttached()) {
+ super.detach();
+ }
+
+ if (portal) {
+ super.attach(portal);
+ }
+
+ this._portal = portal;
+ }
+
+ ngOnDestroy() {
+ super.dispose();
+ this._portal = null;
+ }
+
+ /**
+ * Attach the given ComponentPortal to this PortalHost using the ComponentFactoryResolver.
+ *
+ * @param portal Portal to be attached to the portal host.
+ */
+ attachComponentPortal(portal: ComponentPortal): ComponentRef {
+ portal.setAttachedHost(this);
+
+ // If the portal specifies an origin, use that as the logical location of the component
+ // in the application tree. Otherwise use the location of this PortalHost.
+ let viewContainerRef = portal.viewContainerRef != null ?
+ portal.viewContainerRef :
+ this._viewContainerRef;
+
+ let componentFactory =
+ this._componentFactoryResolver.resolveComponentFactory(portal.component);
+ let ref = viewContainerRef.createComponent(
+ componentFactory, viewContainerRef.length,
+ portal.injector || viewContainerRef.parentInjector);
+
+ super.setDisposeFn(() => ref.destroy());
+ this._portal = portal;
+
+ return ref;
+ }
+
+ /**
+ * Attach the given TemplatePortal to this PortlHost as an embedded View.
+ * @param portal Portal to be attached.
+ */
+ attachTemplatePortal(portal: TemplatePortal): Map {
+ portal.setAttachedHost(this);
+
+ this._viewContainerRef.createEmbeddedView(portal.templateRef);
+ super.setDisposeFn(() => this._viewContainerRef.clear());
+
+ this._portal = portal;
+
+ // TODO(jelbourn): return locals from view
+ return new Map();
+ }
+}
+
+
+@NgModule({
+ exports: [TemplatePortalDirective, PortalHostDirective],
+ declarations: [TemplatePortalDirective, PortalHostDirective],
+})
+export class PortalModule {}
diff --git a/src/lib/core/portal/portal-errors.ts b/src/cdk/portal/portal-errors.ts
similarity index 100%
rename from src/lib/core/portal/portal-errors.ts
rename to src/cdk/portal/portal-errors.ts
diff --git a/src/lib/core/portal/portal.spec.ts b/src/cdk/portal/portal.spec.ts
similarity index 100%
rename from src/lib/core/portal/portal.spec.ts
rename to src/cdk/portal/portal.spec.ts
diff --git a/src/cdk/portal/portal.ts b/src/cdk/portal/portal.ts
new file mode 100644
index 000000000000..d0cf865be5e3
--- /dev/null
+++ b/src/cdk/portal/portal.ts
@@ -0,0 +1,236 @@
+/**
+ * @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 {
+ TemplateRef,
+ ViewContainerRef,
+ ElementRef,
+ ComponentRef,
+ Injector
+} from '@angular/core';
+import {
+ throwNullPortalHostError,
+ throwPortalAlreadyAttachedError,
+ throwNoPortalAttachedError,
+ throwNullPortalError,
+ throwPortalHostAlreadyDisposedError,
+ throwUnknownPortalTypeError
+} from './portal-errors';
+
+
+export interface ComponentType {
+ new (...args: any[]): T;
+}
+
+/**
+ * A `Portal` is something that you want to render somewhere else.
+ * It can be attach to / detached from a `PortalHost`.
+ */
+export abstract class Portal {
+ private _attachedHost: PortalHost | null;
+
+ /** Attach this portal to a host. */
+ attach(host: PortalHost): T {
+ if (host == null) {
+ throwNullPortalHostError();
+ }
+
+ if (host.hasAttached()) {
+ throwPortalAlreadyAttachedError();
+ }
+
+ this._attachedHost = host;
+ return host.attach(this);
+ }
+
+ /** Detach this portal from its host */
+ detach(): void {
+ let host = this._attachedHost;
+
+ if (host == null) {
+ throwNoPortalAttachedError();
+ } else {
+ this._attachedHost = null;
+ host.detach();
+ }
+ }
+
+ /** Whether this portal is attached to a host. */
+ get isAttached(): boolean {
+ return this._attachedHost != null;
+ }
+
+ /**
+ * Sets the PortalHost reference without performing `attach()`. This is used directly by
+ * the PortalHost when it is performing an `attach()` or `detach()`.
+ */
+ setAttachedHost(host: PortalHost | null) {
+ this._attachedHost = host;
+ }
+}
+
+
+/**
+ * A `ComponentPortal` is a portal that instantiates some Component upon attachment.
+ */
+export class ComponentPortal extends Portal> {
+ /** The type of the component that will be instantiated for attachment. */
+ component: ComponentType;
+
+ /**
+ * [Optional] Where the attached component should live in Angular's *logical* component tree.
+ * This is different from where the component *renders*, which is determined by the PortalHost.
+ * The origin is necessary when the host is outside of the Angular application context.
+ */
+ viewContainerRef?: ViewContainerRef | null;
+
+ /** [Optional] Injector used for the instantiation of the component. */
+ injector?: Injector | null;
+
+ constructor(
+ component: ComponentType,
+ viewContainerRef?: ViewContainerRef | null,
+ injector?: Injector | null) {
+ super();
+ this.component = component;
+ this.viewContainerRef = viewContainerRef;
+ this.injector = injector;
+ }
+}
+
+
+/**
+ * A `TemplatePortal` is a portal that represents some embedded template (TemplateRef).
+ */
+export class TemplatePortal extends Portal> {
+ /** The embedded template that will be used to instantiate an embedded View in the host. */
+ templateRef: TemplateRef;
+
+ /** Reference to the ViewContainer into which the template will be stamped out. */
+ viewContainerRef: ViewContainerRef;
+
+ /**
+ * Additional locals for the instantiated embedded view.
+ * These locals can be seen as "exports" for the template, such as how ngFor has
+ * index / event / odd.
+ * See https://angular.io/docs/ts/latest/api/core/EmbeddedViewRef-class.html
+ */
+ locals: Map = new Map();
+
+ constructor(template: TemplateRef, viewContainerRef: ViewContainerRef) {
+ super();
+ this.templateRef = template;
+ this.viewContainerRef = viewContainerRef;
+ }
+
+ get origin(): ElementRef {
+ return this.templateRef.elementRef;
+ }
+
+ attach(host: PortalHost, locals?: Map): Map {
+ this.locals = locals == null ? new Map() : locals;
+ return super.attach(host);
+ }
+
+ detach(): void {
+ this.locals = new Map();
+ return super.detach();
+ }
+}
+
+
+/**
+ * A `PortalHost` is an space that can contain a single `Portal`.
+ */
+export interface PortalHost {
+ attach(portal: Portal): any;
+
+ detach(): any;
+
+ dispose(): void;
+
+ hasAttached(): boolean;
+}
+
+
+/**
+ * Partial implementation of PortalHost that only deals with attaching either a
+ * ComponentPortal or a TemplatePortal.
+ */
+export abstract class BasePortalHost implements PortalHost {
+ /** The portal currently attached to the host. */
+ private _attachedPortal: Portal | null;
+
+ /** A function that will permanently dispose this host. */
+ private _disposeFn: (() => void) | null;
+
+ /** Whether this host has already been permanently disposed. */
+ private _isDisposed: boolean = false;
+
+ /** Whether this host has an attached portal. */
+ hasAttached(): boolean {
+ return !!this._attachedPortal;
+ }
+
+ attach(portal: Portal): any {
+ if (!portal) {
+ throwNullPortalError();
+ }
+
+ if (this.hasAttached()) {
+ throwPortalAlreadyAttachedError();
+ }
+
+ if (this._isDisposed) {
+ throwPortalHostAlreadyDisposedError();
+ }
+
+ if (portal instanceof ComponentPortal) {
+ this._attachedPortal = portal;
+ return this.attachComponentPortal(portal);
+ } else if (portal instanceof TemplatePortal) {
+ this._attachedPortal = portal;
+ return this.attachTemplatePortal(portal);
+ }
+
+ throwUnknownPortalTypeError();
+ }
+
+ abstract attachComponentPortal(portal: ComponentPortal): ComponentRef;
+
+ abstract attachTemplatePortal(portal: TemplatePortal): Map;
+
+ detach(): void {
+ if (this._attachedPortal) {
+ this._attachedPortal.setAttachedHost(null);
+ this._attachedPortal = null;
+ }
+
+ this._invokeDisposeFn();
+ }
+
+ dispose() {
+ if (this.hasAttached()) {
+ this.detach();
+ }
+
+ this._invokeDisposeFn();
+ this._isDisposed = true;
+ }
+
+ setDisposeFn(fn: () => void) {
+ this._disposeFn = fn;
+ }
+
+ private _invokeDisposeFn() {
+ if (this._disposeFn) {
+ this._disposeFn();
+ this._disposeFn = null;
+ }
+ }
+}
diff --git a/src/cdk/public_api.ts b/src/cdk/public_api.ts
index d57df6497263..2f51fd114eb9 100644
--- a/src/cdk/public_api.ts
+++ b/src/cdk/public_api.ts
@@ -6,5 +6,11 @@
* found in the LICENSE file at https://angular.io/license
*/
+export * from './a11y/index';
+export * from './bidi/index';
export * from './coercion/index';
export * from './table/index';
+export * from './platform/index';
+export * from './portal/index';
+export * from './rxjs/index';
+export * from './keyboard/keycodes';
diff --git a/src/cdk/rxjs/index.ts b/src/cdk/rxjs/index.ts
new file mode 100644
index 000000000000..a6f22e575d8e
--- /dev/null
+++ b/src/cdk/rxjs/index.ts
@@ -0,0 +1,10 @@
+/**
+ * @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
+ */
+
+export * from './rx-chain';
+export * from './rx-operators';
diff --git a/src/cdk/rxjs/rx-chain.spec.ts b/src/cdk/rxjs/rx-chain.spec.ts
new file mode 100644
index 000000000000..e9bc10ec5401
--- /dev/null
+++ b/src/cdk/rxjs/rx-chain.spec.ts
@@ -0,0 +1,41 @@
+import {Observable} from 'rxjs/Observable';
+import {of as observableOf} from 'rxjs/observable/of';
+import {RxChain, map, filter, first} from './index';
+
+describe('RxChain', () => {
+ it('should call all of the operators in the chain', () => {
+ let operators = { map, filter, first };
+
+ spyOn(operators, 'map');
+ spyOn(operators, 'filter');
+ spyOn(operators, 'first');
+
+ RxChain.from(observableOf(1, 2, 3))
+ .call(operators.map, i => i * 2)
+ .call(operators.filter, i => i % 2 === 0)
+ .call(operators.first);
+
+ expect(operators.map).toHaveBeenCalled();
+ expect(operators.filter).toHaveBeenCalled();
+ expect(operators.first).toHaveBeenCalled();
+ });
+
+ it('should be able to subscribe', () => {
+ const spy = jasmine.createSpy('subscription spy');
+
+ RxChain.from(observableOf(1, 2))
+ .call(map, i => i * 2)
+ .call(first)
+ .subscribe(spy);
+
+ expect(spy).toHaveBeenCalledWith(2);
+ });
+
+ it('should be able to return the result observable', () => {
+ const chain = RxChain.from(observableOf(1, 2))
+ .call(map, i => i * 2)
+ .call(first);
+
+ expect(chain.result() instanceof Observable).toBe(true);
+ });
+});
diff --git a/src/cdk/rxjs/rx-chain.ts b/src/cdk/rxjs/rx-chain.ts
new file mode 100644
index 000000000000..57b126cead24
--- /dev/null
+++ b/src/cdk/rxjs/rx-chain.ts
@@ -0,0 +1,55 @@
+/**
+ * @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 {Observable} from 'rxjs/Observable';
+import {Subscription} from 'rxjs/Subscription';
+import {StrictRxChain} from './rx-operators';
+
+/**
+ * Utility class used to chain RxJS operators.
+ *
+ * This class is the concrete implementation, but the type used by the user when chaining
+ * is StrictRxChain. The strict chain enforces types on the operators to the same level as
+ * the prototype-added equivalents.
+ */
+export class RxChain {
+ private constructor(private _context: Observable) { }
+
+ /**
+ * Starts a new chain and specifies the initial `this` value.
+ * @param context Initial `this` value for the chain.
+ */
+ static from(context: Observable): StrictRxChain {
+ return new RxChain(context);
+ }
+
+ /**
+ * Invokes an RxJS operator as a part of the chain.
+ * @param operator Operator to be invoked.
+ * @param args Arguments to be passed to the operator.
+ */
+ call(operator: Function, ...args: any[]): RxChain {
+ this._context = operator.call(this._context, ...args);
+ return this;
+ }
+
+ /**
+ * Subscribes to the result of the chain.
+ * @param fn Callback to be invoked when the result emits a value.
+ */
+ subscribe(fn: (t: T) => void): Subscription {
+ return this._context.subscribe(fn);
+ }
+
+ /**
+ * Returns the result of the chain.
+ */
+ result(): Observable {
+ return this._context;
+ }
+}
diff --git a/src/cdk/rxjs/rx-operators.ts b/src/cdk/rxjs/rx-operators.ts
new file mode 100644
index 000000000000..a404ac2a47a1
--- /dev/null
+++ b/src/cdk/rxjs/rx-operators.ts
@@ -0,0 +1,124 @@
+/**
+ * @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 {Observable, ObservableInput} from 'rxjs/Observable';
+import {PartialObserver} from 'rxjs/Observer';
+import {Subscription} from 'rxjs/Subscription';
+import {IScheduler} from 'rxjs/Scheduler';
+import {_finally as _finallyOperator} from 'rxjs/operator/finally';
+import {_catch as _catchOperator} from 'rxjs/operator/catch';
+import {_do as _doOperator} from 'rxjs/operator/do';
+import {map as mapOperator} from 'rxjs/operator/map';
+import {filter as filterOperator} from 'rxjs/operator/filter';
+import {share as shareOperator} from 'rxjs/operator/share';
+import {first as firstOperator} from 'rxjs/operator/first';
+import {switchMap as switchMapOperator} from 'rxjs/operator/switchMap';
+import {startWith as startWithOperator} from 'rxjs/operator/startWith';
+import {debounceTime as debounceTimeOperator} from 'rxjs/operator/debounceTime';
+import {auditTime as auditTimeOperator} from 'rxjs/operator/auditTime';
+import {takeUntil as takeUntilOperator} from 'rxjs/operator/takeUntil';
+
+/**
+ * Represents a strongly-typed chain of RxJS operators.
+ *
+ * We achieve strict type enforcement on the chained operators by creating types that
+ * *unambiguously* match specific rxjs operators. These unambiguous types are created by
+ * intersecting a "brand" to the `typeof` the existing operator. The brand (a class with a private
+ * member) effectively forces nominal typing for the operators. This allows typescript to understand
+ * that, for example, `filter` is *`filter`* and not, say, a map of T => boolean.
+ *
+ * The downside to this approach is that operators must be imported in their type-coerced form
+ * rather than from the normal rxjs location.
+ */
+export interface StrictRxChain {
+ call(operator: mapOperatorType,
+ project: (value: T, index: number) => R, thisArg?: any): StrictRxChain;
+
+ call(operator: switchMapOperatorType,
+ project: (value: T, index: number) => ObservableInput): StrictRxChain;
+
+ call(operator: catchOperatorType,
+ selector: (err: any, caught: Observable) => ObservableInput): StrictRxChain;
+
+ call(operator: filterOperatorType,
+ predicate: (value: T, index: number) => boolean, thisArg?: any): StrictRxChain;
+
+ call(operator: shareOperatorType): StrictRxChain;
+
+ call(operator: finallyOperatorType, action: () => void): StrictRxChain;
+
+ call(operator: doOperatorType, next: (x: T) => void, error?:
+ (e: any) => void, complete?: () => void): StrictRxChain;
+
+ call(operator: doOperatorType, observer: PartialObserver): StrictRxChain;
+
+ call(operator: firstOperatorType, thisArg?: any, defaultValue?: any): StrictRxChain;
+
+ call(operator: firstOperatorType, predicate: (value: T) => boolean): StrictRxChain;
+
+ call(operator: startWithOperatorType, ...args: any[]): StrictRxChain;
+
+ call(operator: debounceTimeOperatorType, dueTime: number,
+ scheduler?: IScheduler): StrictRxChain;
+
+ call(operator: auditTimeOperatorType, duration: number,
+ scheduler?: IScheduler): StrictRxChain;
+
+ call(operator: takeUntilOperatorType, notifier: Observable): StrictRxChain;
+
+ subscribe(fn: (t: T) => void): Subscription;
+
+ result(): Observable;
+}
+
+
+export class FinallyBrand { private _; }
+export class CatchBrand { private _; }
+export class DoBrand { private _; }
+export class MapBrand { private _; }
+export class FilterBrand { private _; }
+export class ShareBrand { private _; }
+export class FirstBrand { private _; }
+export class SwitchMapBrand { private _; }
+export class StartWithBrand { private _; }
+export class DebounceTimeBrand { private _; }
+export class AuditTimeBrand { private _; }
+export class TakeUntilBrand { private _; }
+
+
+export type finallyOperatorType = typeof _finallyOperator & FinallyBrand;
+export type catchOperatorType = typeof _catchOperator & CatchBrand;
+export type doOperatorType = typeof _doOperator & DoBrand;
+export type mapOperatorType = typeof mapOperator & MapBrand;
+export type filterOperatorType = typeof filterOperator & FilterBrand;
+export type shareOperatorType = typeof shareOperator & ShareBrand;
+export type firstOperatorType = typeof firstOperator & FirstBrand;
+export type switchMapOperatorType = typeof switchMapOperator & SwitchMapBrand;
+export type startWithOperatorType = typeof startWithOperator & StartWithBrand;
+export type debounceTimeOperatorType = typeof debounceTimeOperator & DebounceTimeBrand;
+export type auditTimeOperatorType = typeof auditTimeOperator & AuditTimeBrand;
+export type takeUntilOperatorType = typeof takeUntilOperator & TakeUntilBrand;
+
+// We add `Function` to the type intersection to make this nomically different from
+// `finallyOperatorType` while still being structurally the same. Without this, TypeScript tries to
+// reduce `typeof _finallyOperator & FinallyBrand` to `finallyOperatorType` and then fails
+// because `T` isn't known.
+export const finallyOperator =
+ _finallyOperator as typeof _finallyOperator & FinallyBrand & Function;
+export const catchOperator = _catchOperator as typeof _catchOperator & CatchBrand & Function;
+export const doOperator = _doOperator as typeof _doOperator & DoBrand & Function;
+export const map = mapOperator as typeof mapOperator & MapBrand & Function;
+export const filter = filterOperator as typeof filterOperator & FilterBrand & Function;
+export const share = shareOperator as typeof shareOperator & ShareBrand & Function;
+export const first = firstOperator as typeof firstOperator & FirstBrand & Function;
+export const switchMap = switchMapOperator as typeof switchMapOperator & SwitchMapBrand & Function;
+export const startWith = startWithOperator as typeof startWithOperator & StartWithBrand & Function;
+export const debounceTime =
+ debounceTimeOperator as typeof debounceTimeOperator & DebounceTimeBrand & Function;
+export const auditTime = auditTimeOperator as typeof auditTimeOperator & AuditTimeBrand & Function;
+export const takeUntil = takeUntilOperator as typeof takeUntilOperator & TakeUntilBrand & Function;
diff --git a/src/cdk/testing/dispatch-events.ts b/src/cdk/testing/dispatch-events.ts
new file mode 100644
index 000000000000..6e7739c22454
--- /dev/null
+++ b/src/cdk/testing/dispatch-events.ts
@@ -0,0 +1,30 @@
+/**
+ * @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 {createFakeEvent, createKeyboardEvent, createMouseEvent} from './event-objects';
+
+/** Utility to dispatch any event on a Node. */
+export function dispatchEvent(node: Node | Window, event: Event): Event {
+ node.dispatchEvent(event);
+ return event;
+}
+
+/** Shorthand to dispatch a fake event on a specified node. */
+export function dispatchFakeEvent(node: Node | Window, type: string): Event {
+ return dispatchEvent(node, createFakeEvent(type));
+}
+
+/** Shorthand to dispatch a keyboard event with a specified key code. */
+export function dispatchKeyboardEvent(node: Node, type: string, keyCode: number): KeyboardEvent {
+ return dispatchEvent(node, createKeyboardEvent(type, keyCode)) as KeyboardEvent;
+}
+
+/** Shorthand to dispatch a mouse event on the specified coordinates. */
+export function dispatchMouseEvent(node: Node, type: string, x = 0, y = 0): MouseEvent {
+ return dispatchEvent(node, createMouseEvent(type, x, y)) as MouseEvent;
+}
diff --git a/src/cdk/testing/event-objects.ts b/src/cdk/testing/event-objects.ts
new file mode 100644
index 000000000000..6b47eb916633
--- /dev/null
+++ b/src/cdk/testing/event-objects.ts
@@ -0,0 +1,62 @@
+/**
+ * @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
+ */
+
+/** Creates a browser MouseEvent with the specified options. */
+export function createMouseEvent(type: string, x = 0, y = 0) {
+ let event = document.createEvent('MouseEvent');
+
+ event.initMouseEvent(type,
+ false, /* canBubble */
+ false, /* cancelable */
+ window, /* view */
+ 0, /* detail */
+ x, /* screenX */
+ y, /* screenY */
+ x, /* clientX */
+ y, /* clientY */
+ false, /* ctrlKey */
+ false, /* altKey */
+ false, /* shiftKey */
+ false, /* metaKey */
+ 0, /* button */
+ null /* relatedTarget */);
+
+ return event;
+}
+
+/** Dispatches a keydown event from an element. */
+export function createKeyboardEvent(type: string, keyCode: number, target?: Element) {
+ let event = document.createEvent('KeyboardEvent') as any;
+ // Firefox does not support `initKeyboardEvent`, but supports `initKeyEvent`.
+ let initEventFn = (event.initKeyEvent || event.initKeyboardEvent).bind(event);
+ let originalPreventDefault = event.preventDefault;
+
+ initEventFn(type, true, true, window, 0, 0, 0, 0, 0, keyCode);
+
+ // Webkit Browsers don't set the keyCode when calling the init function.
+ // See related bug https://bugs.webkit.org/show_bug.cgi?id=16735
+ Object.defineProperties(event, {
+ keyCode: { get: () => keyCode },
+ target: { get: () => target }
+ });
+
+ // IE won't set `defaultPrevented` on synthetic events so we need to do it manually.
+ event.preventDefault = function() {
+ Object.defineProperty(event, 'defaultPrevented', { get: () => true });
+ return originalPreventDefault.apply(this, arguments);
+ };
+
+ return event;
+}
+
+/** Creates a fake event object with any desired event type. */
+export function createFakeEvent(type: string) {
+ let event = document.createEvent('Event');
+ event.initEvent(type, true, true);
+ return event;
+}
diff --git a/src/lib/core/a11y/README.md b/src/lib/core/a11y/README.md
index a98419c31666..45e45eac659a 100644
--- a/src/lib/core/a11y/README.md
+++ b/src/lib/core/a11y/README.md
@@ -1,30 +1 @@
-# LiveAnnouncer
-`LiveAnnouncer` is a service, which announces messages to several screenreaders.
-
-### Methods
-
-| Name | Description |
-| --- | --- |
-| `announce(message, politeness)` | This announces a text message to the supported screenreaders. The politeness parameter sets the `aria-live` attribute on the announcer element |
-
-### Examples
-The service can be injected in a component.
-```ts
-@Component({
- selector: 'my-component'
- providers: [LiveAnnouncer]
-})
-export class MyComponent {
-
- constructor(live: LiveAnnouncer) {
- live.announce("Hey Google");
- }
-
-}
-```
-
-### Supported Screenreaders
-- JAWS (Windows)
-- NVDA (Windows)
-- VoiceOver (OSX and iOS)
-- TalkBack (Android)
+See [cdk/a11y](../../../cdk/a11y/README.md)
diff --git a/src/lib/core/a11y/fake-mousedown.ts b/src/lib/core/a11y/fake-mousedown.ts
index 87f6eb92592e..024aaef75b48 100644
--- a/src/lib/core/a11y/fake-mousedown.ts
+++ b/src/lib/core/a11y/fake-mousedown.ts
@@ -6,13 +6,4 @@
* found in the LICENSE file at https://angular.io/license
*/
-/**
- * Screenreaders will often fire fake mousedown events when a focusable element
- * is activated using the keyboard. We can typically distinguish between these faked
- * mousedown events and real mousedown events using the "buttons" property. While
- * real mousedowns will indicate the mouse button that was pressed (e.g. "1" for
- * the left mouse button), faked mousedowns will usually set the property value to 0.
- */
-export function isFakeMousedownFromScreenReader(event: MouseEvent): boolean {
- return event.buttons === 0;
-}
+export {isFakeMousedownFromScreenReader} from '@angular/cdk';
diff --git a/src/lib/core/a11y/focus-trap.ts b/src/lib/core/a11y/focus-trap.ts
index 10510b335d17..0c7cc27bec67 100644
--- a/src/lib/core/a11y/focus-trap.ts
+++ b/src/lib/core/a11y/focus-trap.ts
@@ -6,304 +6,14 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {
- Directive,
- ElementRef,
- Input,
- NgZone,
- OnDestroy,
- AfterContentInit,
- Injectable,
-} from '@angular/core';
-import {coerceBooleanProperty} from '@angular/cdk';
-import {InteractivityChecker} from './interactivity-checker';
-import {Platform} from '../platform/platform';
-import {first} from '../rxjs/index';
+export {
+ FocusTrap,
+ FocusTrapFactory,
+ FocusTrapDeprecatedDirective,
+ FocusTrapDirective
+} from '@angular/cdk';
-/**
- * Class that allows for trapping focus within a DOM element.
- *
- * NOTE: This class currently uses a very simple (naive) approach to focus trapping.
- * It assumes that the tab order is the same as DOM order, which is not necessarily true.
- * Things like tabIndex > 0, flex `order`, and shadow roots can cause to two to misalign.
- * This will be replaced with a more intelligent solution before the library is considered stable.
- */
-export class FocusTrap {
- private _startAnchor: HTMLElement | null;
- private _endAnchor: HTMLElement | null;
-
- /** Whether the focus trap is active. */
- get enabled(): boolean { return this._enabled; }
- set enabled(val: boolean) {
- this._enabled = val;
-
- if (this._startAnchor && this._endAnchor) {
- this._startAnchor.tabIndex = this._endAnchor.tabIndex = this._enabled ? 0 : -1;
- }
- }
- private _enabled: boolean = true;
-
- constructor(
- private _element: HTMLElement,
- private _platform: Platform,
- private _checker: InteractivityChecker,
- private _ngZone: NgZone,
- deferAnchors = false) {
-
- if (!deferAnchors) {
- this.attachAnchors();
- }
- }
-
- /** Destroys the focus trap by cleaning up the anchors. */
- destroy() {
- if (this._startAnchor && this._startAnchor.parentNode) {
- this._startAnchor.parentNode.removeChild(this._startAnchor);
- }
-
- if (this._endAnchor && this._endAnchor.parentNode) {
- this._endAnchor.parentNode.removeChild(this._endAnchor);
- }
-
- this._startAnchor = this._endAnchor = null;
- }
-
- /**
- * Inserts the anchors into the DOM. This is usually done automatically
- * in the constructor, but can be deferred for cases like directives with `*ngIf`.
- */
- attachAnchors(): void {
- // If we're not on the browser, there can be no focus to trap.
- if (!this._platform.isBrowser) {
- return;
- }
-
- if (!this._startAnchor) {
- this._startAnchor = this._createAnchor();
- }
-
- if (!this._endAnchor) {
- this._endAnchor = this._createAnchor();
- }
-
- this._ngZone.runOutsideAngular(() => {
- this._startAnchor!.addEventListener('focus', () => this.focusLastTabbableElement());
- this._endAnchor!.addEventListener('focus', () => this.focusFirstTabbableElement());
-
- if (this._element.parentNode) {
- this._element.parentNode.insertBefore(this._startAnchor!, this._element);
- this._element.parentNode.insertBefore(this._endAnchor!, this._element.nextSibling);
- }
- });
- }
-
- /**
- * Waits for the zone to stabilize, then either focuses the first element that the
- * user specified, or the first tabbable element..
- */
- focusInitialElementWhenReady() {
- this._executeOnStable(() => this.focusInitialElement());
- }
-
- /**
- * Waits for the zone to stabilize, then focuses
- * the first tabbable element within the focus trap region.
- */
- focusFirstTabbableElementWhenReady() {
- this._executeOnStable(() => this.focusFirstTabbableElement());
- }
-
- /**
- * Waits for the zone to stabilize, then focuses
- * the last tabbable element within the focus trap region.
- */
- focusLastTabbableElementWhenReady() {
- this._executeOnStable(() => this.focusLastTabbableElement());
- }
-
- /**
- * Get the specified boundary element of the trapped region.
- * @param bound The boundary to get (start or end of trapped region).
- * @returns The boundary element.
- */
- private _getRegionBoundary(bound: 'start' | 'end'): HTMLElement | null {
- // Contains the deprecated version of selector, for temporary backwards comparability.
- let markers = this._element.querySelectorAll(`[cdk-focus-region-${bound}], ` +
- `[cdk-focus-${bound}]`) as NodeListOf;
-
- for (let i = 0; i < markers.length; i++) {
- if (markers[i].hasAttribute(`cdk-focus-${bound}`)) {
- console.warn(`Found use of deprecated attribute 'cdk-focus-${bound}',` +
- ` use 'cdk-focus-region-${bound}' instead.`, markers[i]);
- }
- }
-
- if (bound == 'start') {
- return markers.length ? markers[0] : this._getFirstTabbableElement(this._element);
- }
- return markers.length ?
- markers[markers.length - 1] : this._getLastTabbableElement(this._element);
- }
-
- /** Focuses the element that should be focused when the focus trap is initialized. */
- focusInitialElement() {
- let redirectToElement = this._element.querySelector('[cdk-focus-initial]') as HTMLElement;
- if (redirectToElement) {
- redirectToElement.focus();
- } else {
- this.focusFirstTabbableElement();
- }
- }
-
- /** Focuses the first tabbable element within the focus trap region. */
- focusFirstTabbableElement() {
- let redirectToElement = this._getRegionBoundary('start');
- if (redirectToElement) {
- redirectToElement.focus();
- }
- }
-
- /** Focuses the last tabbable element within the focus trap region. */
- focusLastTabbableElement() {
- let redirectToElement = this._getRegionBoundary('end');
- if (redirectToElement) {
- redirectToElement.focus();
- }
- }
-
- /** Get the first tabbable element from a DOM subtree (inclusive). */
- private _getFirstTabbableElement(root: HTMLElement): HTMLElement | null {
- if (this._checker.isFocusable(root) && this._checker.isTabbable(root)) {
- return root;
- }
-
- // Iterate in DOM order. Note that IE doesn't have `children` for SVG so we fall
- // back to `childNodes` which includes text nodes, comments etc.
- let children = root.children || root.childNodes;
-
- for (let i = 0; i < children.length; i++) {
- let tabbableChild = children[i].nodeType === Node.ELEMENT_NODE ?
- this._getFirstTabbableElement(children[i] as HTMLElement) :
- null;
-
- if (tabbableChild) {
- return tabbableChild;
- }
- }
-
- return null;
- }
-
- /** Get the last tabbable element from a DOM subtree (inclusive). */
- private _getLastTabbableElement(root: HTMLElement): HTMLElement | null {
- if (this._checker.isFocusable(root) && this._checker.isTabbable(root)) {
- return root;
- }
-
- // Iterate in reverse DOM order.
- let children = root.children || root.childNodes;
-
- for (let i = children.length - 1; i >= 0; i--) {
- let tabbableChild = children[i].nodeType === Node.ELEMENT_NODE ?
- this._getLastTabbableElement(children[i] as HTMLElement) :
- null;
-
- if (tabbableChild) {
- return tabbableChild;
- }
- }
-
- return null;
- }
-
- /** Creates an anchor element. */
- private _createAnchor(): HTMLElement {
- let anchor = document.createElement('div');
- anchor.tabIndex = this._enabled ? 0 : -1;
- anchor.classList.add('cdk-visually-hidden');
- anchor.classList.add('cdk-focus-trap-anchor');
- return anchor;
- }
-
- /** Executes a function when the zone is stable. */
- private _executeOnStable(fn: () => any): void {
- if (this._ngZone.isStable) {
- fn();
- } else {
- first.call(this._ngZone.onStable).subscribe(fn);
- }
- }
-}
-
-
-/** Factory that allows easy instantiation of focus traps. */
-@Injectable()
-export class FocusTrapFactory {
- constructor(
- private _checker: InteractivityChecker,
- private _platform: Platform,
- private _ngZone: NgZone) { }
-
- create(element: HTMLElement, deferAnchors = false): FocusTrap {
- return new FocusTrap(element, this._platform, this._checker, this._ngZone, deferAnchors);
- }
-}
-
-
-/**
- * Directive for trapping focus within a region.
- * @deprecated
- */
-@Directive({
- selector: 'cdk-focus-trap',
-})
-export class FocusTrapDeprecatedDirective implements OnDestroy, AfterContentInit {
- focusTrap: FocusTrap;
-
- /** Whether the focus trap is active. */
- @Input()
- get disabled(): boolean { return !this.focusTrap.enabled; }
- set disabled(val: boolean) {
- this.focusTrap.enabled = !coerceBooleanProperty(val);
- }
-
- constructor(private _elementRef: ElementRef, private _focusTrapFactory: FocusTrapFactory) {
- this.focusTrap = this._focusTrapFactory.create(this._elementRef.nativeElement, true);
- }
-
- ngOnDestroy() {
- this.focusTrap.destroy();
- }
-
- ngAfterContentInit() {
- this.focusTrap.attachAnchors();
- }
-}
-
-
-/** Directive for trapping focus within a region. */
-@Directive({
- selector: '[cdkTrapFocus]',
- exportAs: 'cdkTrapFocus',
-})
-export class FocusTrapDirective implements OnDestroy, AfterContentInit {
- focusTrap: FocusTrap;
-
- /** Whether the focus trap is active. */
- @Input('cdkTrapFocus')
- get enabled(): boolean { return this.focusTrap.enabled; }
- set enabled(value: boolean) { this.focusTrap.enabled = coerceBooleanProperty(value); }
- constructor(private _elementRef: ElementRef, private _focusTrapFactory: FocusTrapFactory) {
- this.focusTrap = this._focusTrapFactory.create(this._elementRef.nativeElement, true);
- }
- ngOnDestroy() {
- this.focusTrap.destroy();
- }
- ngAfterContentInit() {
- this.focusTrap.attachAnchors();
- }
-}
diff --git a/src/lib/core/a11y/index.ts b/src/lib/core/a11y/index.ts
index 4e8d6a5eac76..581029af911b 100644
--- a/src/lib/core/a11y/index.ts
+++ b/src/lib/core/a11y/index.ts
@@ -6,17 +6,4 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {NgModule} from '@angular/core';
-import {FocusTrapDirective, FocusTrapDeprecatedDirective, FocusTrapFactory} from './focus-trap';
-import {LIVE_ANNOUNCER_PROVIDER} from './live-announcer';
-import {InteractivityChecker} from './interactivity-checker';
-import {CommonModule} from '@angular/common';
-import {PlatformModule} from '../platform/index';
-
-@NgModule({
- imports: [CommonModule, PlatformModule],
- declarations: [FocusTrapDirective, FocusTrapDeprecatedDirective],
- exports: [FocusTrapDirective, FocusTrapDeprecatedDirective],
- providers: [InteractivityChecker, FocusTrapFactory, LIVE_ANNOUNCER_PROVIDER]
-})
-export class A11yModule {}
+export {A11yModule} from '@angular/cdk';
diff --git a/src/lib/core/a11y/interactivity-checker.ts b/src/lib/core/a11y/interactivity-checker.ts
index 035e27001506..cd8a2d6ae708 100644
--- a/src/lib/core/a11y/interactivity-checker.ts
+++ b/src/lib/core/a11y/interactivity-checker.ts
@@ -6,239 +6,4 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Injectable} from '@angular/core';
-import {Platform} from '../platform/platform';
-
-/**
- * The InteractivityChecker leans heavily on the ally.js accessibility utilities.
- * Methods like `isTabbable` are only covering specific edge-cases for the browsers which are
- * supported.
- */
-
-/**
- * Utility for checking the interactivity of an element, such as whether is is focusable or
- * tabbable.
- */
-@Injectable()
-export class InteractivityChecker {
-
- constructor(private _platform: Platform) {}
-
- /**
- * Gets whether an element is disabled.
- *
- * @param element Element to be checked.
- * @returns Whether the element is disabled.
- */
- isDisabled(element: HTMLElement): boolean {
- // This does not capture some cases, such as a non-form control with a disabled attribute or
- // a form control inside of a disabled form, but should capture the most common cases.
- return element.hasAttribute('disabled');
- }
-
- /**
- * Gets whether an element is visible for the purposes of interactivity.
- *
- * This will capture states like `display: none` and `visibility: hidden`, but not things like
- * being clipped by an `overflow: hidden` parent or being outside the viewport.
- *
- * @returns Whether the element is visible.
- */
- isVisible(element: HTMLElement): boolean {
- return hasGeometry(element) && getComputedStyle(element).visibility === 'visible';
- }
-
- /**
- * Gets whether an element can be reached via Tab key.
- * Assumes that the element has already been checked with isFocusable.
- *
- * @param element Element to be checked.
- * @returns Whether the element is tabbable.
- */
- isTabbable(element: HTMLElement): boolean {
- // Nothing is tabbable on the the server 😎
- if (!this._platform.isBrowser) {
- return false;
- }
-
- let frameElement = getWindow(element).frameElement as HTMLElement;
-
- if (frameElement) {
-
- let frameType = frameElement && frameElement.nodeName.toLowerCase();
-
- // Frame elements inherit their tabindex onto all child elements.
- if (getTabIndexValue(frameElement) === -1) {
- return false;
- }
-
- // Webkit and Blink consider anything inside of an element as non-tabbable.
- if ((this._platform.BLINK || this._platform.WEBKIT) && frameType === 'object') {
- return false;
- }
-
- // Webkit and Blink disable tabbing to an element inside of an invisible frame.
- if ((this._platform.BLINK || this._platform.WEBKIT) && !this.isVisible(frameElement)) {
- return false;
- }
-
- }
-
- let nodeName = element.nodeName.toLowerCase();
- let tabIndexValue = getTabIndexValue(element);
-
- if (element.hasAttribute('contenteditable')) {
- return tabIndexValue !== -1;
- }
-
- if (nodeName === 'iframe') {
- // The frames may be tabbable depending on content, but it's not possibly to reliably
- // investigate the content of the frames.
- return false;
- }
-
- if (nodeName === 'audio') {
- if (!element.hasAttribute('controls')) {
- // By default an element without the controls enabled is not tabbable.
- return false;
- } else if (this._platform.BLINK) {
- // In Blink elements are always tabbable.
- return true;
- }
- }
-
- if (nodeName === 'video') {
- if (!element.hasAttribute('controls') && this._platform.TRIDENT) {
- // In Trident a element without the controls enabled is not tabbable.
- return false;
- } else if (this._platform.BLINK || this._platform.FIREFOX) {
- // In Chrome and Firefox elements are always tabbable.
- return true;
- }
- }
-
- if (nodeName === 'object' && (this._platform.BLINK || this._platform.WEBKIT)) {
- // In all Blink and WebKit based browsers elements are never tabbable.
- return false;
- }
-
- // In iOS the browser only considers some specific elements as tabbable.
- if (this._platform.WEBKIT && this._platform.IOS && !isPotentiallyTabbableIOS(element)) {
- return false;
- }
-
- return element.tabIndex >= 0;
- }
-
- /**
- * Gets whether an element can be focused by the user.
- *
- * @param element Element to be checked.
- * @returns Whether the element is focusable.
- */
- isFocusable(element: HTMLElement): boolean {
- // Perform checks in order of left to most expensive.
- // Again, naive approach that does not capture many edge cases and browser quirks.
- return isPotentiallyFocusable(element) && !this.isDisabled(element) && this.isVisible(element);
- }
-
-}
-
-/** Checks whether the specified element has any geometry / rectangles. */
-function hasGeometry(element: HTMLElement): boolean {
- // Use logic from jQuery to check for an invisible element.
- // See https://github.com/jquery/jquery/blob/master/src/css/hiddenVisibleSelectors.js#L12
- return !!(element.offsetWidth || element.offsetHeight || element.getClientRects().length);
-}
-
-/** Gets whether an element's */
-function isNativeFormElement(element: Node) {
- let nodeName = element.nodeName.toLowerCase();
- return nodeName === 'input' ||
- nodeName === 'select' ||
- nodeName === 'button' ||
- nodeName === 'textarea';
-}
-
-/** Gets whether an element is an . */
-function isHiddenInput(element: HTMLElement): boolean {
- return isInputElement(element) && element.type == 'hidden';
-}
-
-/** Gets whether an element is an anchor that has an href attribute. */
-function isAnchorWithHref(element: HTMLElement): boolean {
- return isAnchorElement(element) && element.hasAttribute('href');
-}
-
-/** Gets whether an element is an input element. */
-function isInputElement(element: HTMLElement): element is HTMLInputElement {
- return element.nodeName.toLowerCase() == 'input';
-}
-
-/** Gets whether an element is an anchor element. */
-function isAnchorElement(element: HTMLElement): element is HTMLAnchorElement {
- return element.nodeName.toLowerCase() == 'a';
-}
-
-/** Gets whether an element has a valid tabindex. */
-function hasValidTabIndex(element: HTMLElement): boolean {
- if (!element.hasAttribute('tabindex') || element.tabIndex === undefined) {
- return false;
- }
-
- let tabIndex = element.getAttribute('tabindex');
-
- // IE11 parses tabindex="" as the value "-32768"
- if (tabIndex == '-32768') {
- return false;
- }
-
- return !!(tabIndex && !isNaN(parseInt(tabIndex, 10)));
-}
-
-/**
- * Returns the parsed tabindex from the element attributes instead of returning the
- * evaluated tabindex from the browsers defaults.
- */
-function getTabIndexValue(element: HTMLElement): number | null {
- if (!hasValidTabIndex(element)) {
- return null;
- }
-
- // See browser issue in Gecko https://bugzilla.mozilla.org/show_bug.cgi?id=1128054
- const tabIndex = parseInt(element.getAttribute('tabindex') || '', 10);
-
- return isNaN(tabIndex) ? -1 : tabIndex;
-}
-
-/** Checks whether the specified element is potentially tabbable on iOS */
-function isPotentiallyTabbableIOS(element: HTMLElement): boolean {
- let nodeName = element.nodeName.toLowerCase();
- let inputType = nodeName === 'input' && (element as HTMLInputElement).type;
-
- return inputType === 'text'
- || inputType === 'password'
- || nodeName === 'select'
- || nodeName === 'textarea';
-}
-
-/**
- * Gets whether an element is potentially focusable without taking current visible/disabled state
- * into account.
- */
-function isPotentiallyFocusable(element: HTMLElement): boolean {
- // Inputs are potentially focusable *unless* they're type="hidden".
- if (isHiddenInput(element)) {
- return false;
- }
-
- return isNativeFormElement(element) ||
- isAnchorWithHref(element) ||
- element.hasAttribute('contenteditable') ||
- hasValidTabIndex(element);
-}
-
-/** Gets the parent window of a DOM node with regards of being inside of an iframe. */
-function getWindow(node: HTMLElement): Window {
- return node.ownerDocument.defaultView || window;
-}
+export {InteractivityChecker} from '@angular/cdk';
diff --git a/src/lib/core/a11y/list-key-manager.ts b/src/lib/core/a11y/list-key-manager.ts
index a7d3bc7a9706..50967d662973 100644
--- a/src/lib/core/a11y/list-key-manager.ts
+++ b/src/lib/core/a11y/list-key-manager.ts
@@ -6,172 +6,6 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {QueryList} from '@angular/core';
-import {UP_ARROW, DOWN_ARROW, TAB} from '../core';
-import {Observable} from 'rxjs/Observable';
-import {Subject} from 'rxjs/Subject';
+export {CanDisable, ListKeyManager} from '@angular/cdk';
-/**
- * This interface is for items that can be disabled. The type passed into
- * ListKeyManager must extend this interface.
- */
-export interface CanDisable {
- disabled?: boolean;
-}
-
-/**
- * This class manages keyboard events for selectable lists. If you pass it a query list
- * of items, it will set the active item correctly when arrow events occur.
- */
-export class ListKeyManager {
- private _activeItemIndex: number = -1;
- private _activeItem: T;
- private _tabOut = new Subject();
- private _wrap: boolean = false;
-
- constructor(private _items: QueryList) { }
-
- /**
- * Turns on wrapping mode, which ensures that the active item will wrap to
- * the other end of list when there are no more items in the given direction.
- *
- * @returns The ListKeyManager that the method was called on.
- */
- withWrap(): this {
- this._wrap = true;
- return this;
- }
-
- /**
- * Sets the active item to the item at the index specified.
- *
- * @param index The index of the item to be set as active.
- */
- setActiveItem(index: number): void {
- this._activeItemIndex = index;
- this._activeItem = this._items.toArray()[index];
- }
-
- /**
- * Sets the active item depending on the key event passed in.
- * @param event Keyboard event to be used for determining which element should be active.
- */
- onKeydown(event: KeyboardEvent): void {
- switch (event.keyCode) {
- case DOWN_ARROW:
- this.setNextItemActive();
- break;
- case UP_ARROW:
- this.setPreviousItemActive();
- break;
- case TAB:
- // Note that we shouldn't prevent the default action on tab.
- this._tabOut.next(null);
- return;
- default:
- return;
- }
-
- event.preventDefault();
- }
-
- /** Returns the index of the currently active item. */
- get activeItemIndex(): number | null {
- return this._activeItemIndex;
- }
-
- /** Returns the currently active item. */
- get activeItem(): T | null {
- return this._activeItem;
- }
-
- /** Sets the active item to the first enabled item in the list. */
- setFirstItemActive(): void {
- this._setActiveItemByIndex(0, 1);
- }
-
- /** Sets the active item to the last enabled item in the list. */
- setLastItemActive(): void {
- this._setActiveItemByIndex(this._items.length - 1, -1);
- }
-
- /** Sets the active item to the next enabled item in the list. */
- setNextItemActive(): void {
- this._activeItemIndex < 0 ? this.setFirstItemActive() : this._setActiveItemByDelta(1);
- }
-
- /** Sets the active item to a previous enabled item in the list. */
- setPreviousItemActive(): void {
- this._activeItemIndex < 0 && this._wrap ? this.setLastItemActive()
- : this._setActiveItemByDelta(-1);
- }
-
- /**
- * Allows setting of the activeItemIndex without any other effects.
- * @param index The new activeItemIndex.
- */
- updateActiveItemIndex(index: number) {
- this._activeItemIndex = index;
- }
-
- /**
- * Observable that emits any time the TAB key is pressed, so components can react
- * when focus is shifted off of the list.
- */
- get tabOut(): Observable {
- return this._tabOut.asObservable();
- }
-
- /**
- * This method sets the active item, given a list of items and the delta between the
- * currently active item and the new active item. It will calculate differently
- * depending on whether wrap mode is turned on.
- */
- private _setActiveItemByDelta(delta: number, items = this._items.toArray()): void {
- this._wrap ? this._setActiveInWrapMode(delta, items)
- : this._setActiveInDefaultMode(delta, items);
- }
-
- /**
- * Sets the active item properly given "wrap" mode. In other words, it will continue to move
- * down the list until it finds an item that is not disabled, and it will wrap if it
- * encounters either end of the list.
- */
- private _setActiveInWrapMode(delta: number, items: T[]): void {
- // when active item would leave menu, wrap to beginning or end
- this._activeItemIndex =
- (this._activeItemIndex + delta + items.length) % items.length;
-
- // skip all disabled menu items recursively until an enabled one is reached
- if (items[this._activeItemIndex].disabled) {
- this._setActiveInWrapMode(delta, items);
- } else {
- this.setActiveItem(this._activeItemIndex);
- }
- }
-
- /**
- * Sets the active item properly given the default mode. In other words, it will
- * continue to move down the list until it finds an item that is not disabled. If
- * it encounters either end of the list, it will stop and not wrap.
- */
- private _setActiveInDefaultMode(delta: number, items: T[]): void {
- this._setActiveItemByIndex(this._activeItemIndex + delta, delta, items);
- }
-
- /**
- * Sets the active item to the first enabled item starting at the index specified. If the
- * item is disabled, it will move in the fallbackDelta direction until it either
- * finds an enabled item or encounters the end of the list.
- */
- private _setActiveItemByIndex(index: number, fallbackDelta: number,
- items = this._items.toArray()): void {
- if (!items[index]) { return; }
- while (items[index].disabled) {
- index += fallbackDelta;
- if (!items[index]) { return; }
- }
- this.setActiveItem(index);
- }
-}
diff --git a/src/lib/core/a11y/live-announcer.ts b/src/lib/core/a11y/live-announcer.ts
index a827c47238b9..2070aedb1613 100644
--- a/src/lib/core/a11y/live-announcer.ts
+++ b/src/lib/core/a11y/live-announcer.ts
@@ -6,90 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {
- Injectable,
- InjectionToken,
- Optional,
- Inject,
- SkipSelf,
-} from '@angular/core';
-import {Platform} from '../platform/platform';
-
-
-export const LIVE_ANNOUNCER_ELEMENT_TOKEN = new InjectionToken('liveAnnouncerElement');
-
-/** Possible politeness levels. */
-export type AriaLivePoliteness = 'off' | 'polite' | 'assertive';
-
-@Injectable()
-export class LiveAnnouncer {
-
- private _liveElement: Element;
-
- constructor(
- @Optional() @Inject(LIVE_ANNOUNCER_ELEMENT_TOKEN) elementToken: any,
- platform: Platform) {
- // Only do anything if we're on the browser platform.
- if (platform.isBrowser) {
- // We inject the live element as `any` because the constructor signature cannot reference
- // browser globals (HTMLElement) on non-browser environments, since having a class decorator
- // causes TypeScript to preserve the constructor signature types.
- this._liveElement = elementToken || this._createLiveElement();
- }
- }
-
- /**
- * Announces a message to screenreaders.
- * @param message Message to be announced to the screenreader
- * @param politeness The politeness of the announcer element
- */
- announce(message: string, politeness: AriaLivePoliteness = 'polite'): void {
- this._liveElement.textContent = '';
-
- // TODO: ensure changing the politeness works on all environments we support.
- this._liveElement.setAttribute('aria-live', politeness);
-
- // This 100ms timeout is necessary for some browser + screen-reader combinations:
- // - Both JAWS and NVDA over IE11 will not announce anything without a non-zero timeout.
- // - With Chrome and IE11 with NVDA or JAWS, a repeated (identical) message won't be read a
- // second time without clearing and then using a non-zero delay.
- // (using JAWS 17 at time of this writing).
- setTimeout(() => this._liveElement.textContent = message, 100);
- }
-
- /** Removes the aria-live element from the DOM. */
- _removeLiveElement() {
- if (this._liveElement && this._liveElement.parentNode) {
- this._liveElement.parentNode.removeChild(this._liveElement);
- }
- }
-
- private _createLiveElement(): Element {
- let liveEl = document.createElement('div');
-
- liveEl.classList.add('cdk-visually-hidden');
- liveEl.setAttribute('aria-atomic', 'true');
- liveEl.setAttribute('aria-live', 'polite');
-
- document.body.appendChild(liveEl);
-
- return liveEl;
- }
-
-}
-
-export function LIVE_ANNOUNCER_PROVIDER_FACTORY(
- parentDispatcher: LiveAnnouncer, liveElement: any, platform: Platform) {
- return parentDispatcher || new LiveAnnouncer(liveElement, platform);
-}
-
-export const LIVE_ANNOUNCER_PROVIDER = {
- // If there is already a LiveAnnouncer available, use that. Otherwise, provide a new one.
- provide: LiveAnnouncer,
- deps: [
- [new Optional(), new SkipSelf(), LiveAnnouncer],
- [new Optional(), new Inject(LIVE_ANNOUNCER_ELEMENT_TOKEN)],
- Platform,
- ],
- useFactory: LIVE_ANNOUNCER_PROVIDER_FACTORY
-};
+export {
+ AriaLivePoliteness,
+ LIVE_ANNOUNCER_ELEMENT_TOKEN,
+ LiveAnnouncer,
+ LIVE_ANNOUNCER_PROVIDER_FACTORY,
+ LIVE_ANNOUNCER_PROVIDER
+} from '@angular/cdk';
diff --git a/src/lib/core/bidi/dir.ts b/src/lib/core/bidi/dir.ts
index b25d6418b1ad..31a3054787dd 100644
--- a/src/lib/core/bidi/dir.ts
+++ b/src/lib/core/bidi/dir.ts
@@ -6,61 +6,5 @@
* 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();
-
- /** @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;
- }
-}
+export {Dir} from '@angular/cdk';
diff --git a/src/lib/core/bidi/directionality.ts b/src/lib/core/bidi/directionality.ts
index c0c4be344e9a..aa44238be803 100644
--- a/src/lib/core/bidi/directionality.ts
+++ b/src/lib/core/bidi/directionality.ts
@@ -6,60 +6,10 @@
* 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('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();
-
- 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;
- }
- }
-}
-
-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
-};
+export {
+ Direction,
+ DIR_DOCUMENT,
+ Directionality,
+ DIRECTIONALITY_PROVIDER_FACTORY,
+ DIRECTIONALITY_PROVIDER,
+} from '@angular/cdk';
diff --git a/src/lib/core/bidi/index.ts b/src/lib/core/bidi/index.ts
index 8986092c0f85..9a19368556dd 100644
--- a/src/lib/core/bidi/index.ts
+++ b/src/lib/core/bidi/index.ts
@@ -6,26 +6,12 @@
* 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 { }
+ Dir,
+ BidiModule,
+} from '@angular/cdk';
diff --git a/src/lib/core/keyboard/keycodes.ts b/src/lib/core/keyboard/keycodes.ts
index 0ddf23c4798c..bdd8223be824 100644
--- a/src/lib/core/keyboard/keycodes.ts
+++ b/src/lib/core/keyboard/keycodes.ts
@@ -6,26 +6,20 @@
* found in the LICENSE file at https://angular.io/license
*/
-// Due to a bug in the ChromeDriver, Angular keyboard events are not triggered by `sendKeys`
-// during E2E tests when using dot notation such as `(keydown.rightArrow)`. To get around this,
-// we are temporarily using a single (keydown) handler.
-// See: https://github.com/angular/angular/issues/9419
-export const UP_ARROW = 38;
-export const DOWN_ARROW = 40;
-export const RIGHT_ARROW = 39;
-export const LEFT_ARROW = 37;
-
-export const PAGE_UP = 33;
-export const PAGE_DOWN = 34;
-
-export const HOME = 36;
-export const END = 35;
-
-export const ENTER = 13;
-export const SPACE = 32;
-export const TAB = 9;
-
-export const ESCAPE = 27;
-export const BACKSPACE = 8;
-export const DELETE = 46;
+export {
+ UP_ARROW,
+ DOWN_ARROW,
+ RIGHT_ARROW,
+ LEFT_ARROW,
+ PAGE_UP,
+ PAGE_DOWN,
+ HOME,
+ END,
+ ENTER,
+ SPACE,
+ TAB,
+ ESCAPE,
+ BACKSPACE,
+ DELETE
+} from '@angular/cdk';
diff --git a/src/lib/core/platform/features.ts b/src/lib/core/platform/features.ts
index 67dfeb589e0f..212bc31fedce 100644
--- a/src/lib/core/platform/features.ts
+++ b/src/lib/core/platform/features.ts
@@ -6,59 +6,4 @@
* found in the LICENSE file at https://angular.io/license
*/
-/** Cached result Set of input types support by the current browser. */
-let supportedInputTypes: Set;
-
-/** Types of that *might* be supported. */
-const candidateInputTypes = [
- // `color` must come first. Chrome 56 shows a warning if we change the type to `color` after
- // first changing it to something else:
- // The specified value "" does not conform to the required format.
- // The format is "#rrggbb" where rr, gg, bb are two-digit hexadecimal numbers.
- 'color',
- 'button',
- 'checkbox',
- 'date',
- 'datetime-local',
- 'email',
- 'file',
- 'hidden',
- 'image',
- 'month',
- 'number',
- 'password',
- 'radio',
- 'range',
- 'reset',
- 'search',
- 'submit',
- 'tel',
- 'text',
- 'time',
- 'url',
- 'week',
-];
-
-/** @returns The input types supported by this browser. */
-export function getSupportedInputTypes(): Set {
- // Result is cached.
- if (supportedInputTypes) {
- return supportedInputTypes;
- }
-
- // We can't check if an input type is not supported until we're on the browser, so say that
- // everything is supported when not on the browser. We don't use `Platform` here since it's
- // just a helper function and can't inject it.
- if (typeof document !== 'object' || !document) {
- supportedInputTypes = new Set(candidateInputTypes);
- return supportedInputTypes;
- }
-
- let featureTestInput = document.createElement('input');
- supportedInputTypes = new Set(candidateInputTypes.filter(value => {
- featureTestInput.setAttribute('type', value);
- return featureTestInput.type === value;
- }));
-
- return supportedInputTypes;
-}
+export {getSupportedInputTypes} from '@angular/cdk';
diff --git a/src/lib/core/platform/index.ts b/src/lib/core/platform/index.ts
index ea7a9a4b4cf4..ef07ff616781 100644
--- a/src/lib/core/platform/index.ts
+++ b/src/lib/core/platform/index.ts
@@ -6,15 +6,6 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {NgModule} from '@angular/core';
-import {Platform} from './platform';
-
-
-@NgModule({
- providers: [Platform]
-})
-export class PlatformModule {}
-
-
+export {PlatformModule} from '@angular/cdk';
export * from './platform';
export * from './features';
diff --git a/src/lib/core/platform/platform.ts b/src/lib/core/platform/platform.ts
index 11e12b85eb71..1aca01fd9aac 100755
--- a/src/lib/core/platform/platform.ts
+++ b/src/lib/core/platform/platform.ts
@@ -6,48 +6,4 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Injectable} from '@angular/core';
-
-// Whether the current platform supports the V8 Break Iterator. The V8 check
-// is necessary to detect all Blink based browsers.
-const hasV8BreakIterator = (typeof(Intl) !== 'undefined' && (Intl as any).v8BreakIterator);
-
-/**
- * Service to detect the current platform by comparing the userAgent strings and
- * checking browser-specific global properties.
- * @docs-private
- */
-@Injectable()
-export class Platform {
- isBrowser: boolean = typeof document === 'object' && !!document;
-
- /** Layout Engines */
- EDGE = this.isBrowser && /(edge)/i.test(navigator.userAgent);
- TRIDENT = this.isBrowser && /(msie|trident)/i.test(navigator.userAgent);
-
- // EdgeHTML and Trident mock Blink specific things and need to be excluded from this check.
- BLINK = this.isBrowser &&
- (!!((window as any).chrome || hasV8BreakIterator) && !!CSS && !this.EDGE && !this.TRIDENT);
-
- // Webkit is part of the userAgent in EdgeHTML, Blink and Trident. Therefore we need to
- // ensure that Webkit runs standalone and is not used as another engine's base.
- WEBKIT = this.isBrowser &&
- /AppleWebKit/i.test(navigator.userAgent) && !this.BLINK && !this.EDGE && !this.TRIDENT;
-
- /** Browsers and Platform Types */
- IOS = this.isBrowser && /iPad|iPhone|iPod/.test(navigator.userAgent) && !(window as any).MSStream;
-
- // It's difficult to detect the plain Gecko engine, because most of the browsers identify
- // them self as Gecko-like browsers and modify the userAgent's according to that.
- // Since we only cover one explicit Firefox case, we can simply check for Firefox
- // instead of having an unstable check for Gecko.
- FIREFOX = this.isBrowser && /(firefox|minefield)/i.test(navigator.userAgent);
-
- // Trident on mobile adds the android platform to the userAgent to trick detections.
- ANDROID = this.isBrowser && /android/i.test(navigator.userAgent) && !this.TRIDENT;
-
- // Safari browsers will include the Safari keyword in their userAgent. Some browsers may fake
- // this and just place the Safari keyword in the userAgent. To be more safe about Safari every
- // Safari browser should also use Webkit as its layout engine.
- SAFARI = this.isBrowser && /safari/i.test(navigator.userAgent) && this.WEBKIT;
-}
+export {Platform} from '@angular/cdk';
diff --git a/src/lib/core/portal/README.md b/src/lib/core/portal/README.md
index a9259382e9e7..fff16a0b729b 100644
--- a/src/lib/core/portal/README.md
+++ b/src/lib/core/portal/README.md
@@ -1,72 +1 @@
-# Portals
-
-### Overview
-
-A `Portal `is a piece of UI that can be dynamically rendered to an open slot on the page.
-
-The "piece of UI" can be either a `Component` or a `TemplateRef`.
-
-The "open slot" is a `PortalHost`.
-
-Portals and PortalHosts are low-level building blocks that other concepts, such as overlays, can
-be built upon.
-
-##### `Portal`
-| Method | Description |
-| --- | --- |
-| `attach(PortalHost): Promise` | Attaches the portal to a host. |
-| `detach(): Promise` | Detaches the portal from its host. |
-| `isAttached: boolean` | Whether the portal is attached. |
-
-##### `PortalHost`
-| Method | Description |
-| --- | --- |
-| `attach(Portal): Promise` | Attaches a portal to the host. |
-| `detach(): Promise` | Detaches the portal from the host. |
-| `dispose(): Promise` | Permanently dispose the host. |
-| `hasAttached: boolean` | Whether a portal is attached to the host. |
-
-
-
-
-### Using portals
-
-
-
-##### `TemplatePortalDirective`
-Used to get a portal from a ``. `TemplatePortalDirectives` *is* a `Portal`.
-
-Usage:
-```html
-
- The content of this template is captured by the portal.
-
-
-
-
-
-
- The content of this template is captured by the portal.
-
-```
-
-A component can use `@ViewChild` or `@ViewChildren` to get a reference to a
-`TemplatePortalDirective`.
-
-##### `ComponentPortal`
-Used to create a portal from a component type.
-
-Usage:
-```ts
-this.userSettingsPortal = new ComponentPortal(UserSettingsComponent);
-```
-
-
-##### `PortalHostDirective`
-Used to add a portal host to a template. `PortalHostDirective` *is* a `PortalHost`.
-
-Usage:
-```html
-
-
-```
+See [cdk/portal](../../../cdk/portal/README.md)
diff --git a/src/lib/core/portal/dom-portal-host.ts b/src/lib/core/portal/dom-portal-host.ts
index dc64fdc88bc2..60c04a933405 100644
--- a/src/lib/core/portal/dom-portal-host.ts
+++ b/src/lib/core/portal/dom-portal-host.ts
@@ -5,103 +5,4 @@
* 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 {
- ComponentFactoryResolver,
- ComponentRef,
- EmbeddedViewRef,
- ApplicationRef,
- Injector,
-} from '@angular/core';
-import {BasePortalHost, ComponentPortal, TemplatePortal} from './portal';
-
-
-/**
- * A PortalHost for attaching portals to an arbitrary DOM element outside of the Angular
- * application context.
- *
- * This is the only part of the portal core that directly touches the DOM.
- */
-export class DomPortalHost extends BasePortalHost {
- constructor(
- private _hostDomElement: Element,
- private _componentFactoryResolver: ComponentFactoryResolver,
- private _appRef: ApplicationRef,
- private _defaultInjector: Injector) {
- super();
- }
-
- /**
- * Attach the given ComponentPortal to DOM element using the ComponentFactoryResolver.
- * @param portal Portal to be attached
- */
- attachComponentPortal(portal: ComponentPortal): ComponentRef {
- let componentFactory = this._componentFactoryResolver.resolveComponentFactory(portal.component);
- let componentRef: ComponentRef;
-
- // If the portal specifies a ViewContainerRef, we will use that as the attachment point
- // for the component (in terms of Angular's component tree, not rendering).
- // When the ViewContainerRef is missing, we use the factory to create the component directly
- // and then manually attach the view to the application.
- if (portal.viewContainerRef) {
- componentRef = portal.viewContainerRef.createComponent(
- componentFactory,
- portal.viewContainerRef.length,
- portal.injector || portal.viewContainerRef.parentInjector);
-
- this.setDisposeFn(() => componentRef.destroy());
- } else {
- componentRef = componentFactory.create(portal.injector || this._defaultInjector);
- this._appRef.attachView(componentRef.hostView);
- this.setDisposeFn(() => {
- this._appRef.detachView(componentRef.hostView);
- componentRef.destroy();
- });
- }
- // At this point the component has been instantiated, so we move it to the location in the DOM
- // where we want it to be rendered.
- this._hostDomElement.appendChild(this._getComponentRootNode(componentRef));
-
- return componentRef;
- }
-
- /**
- * Attaches a template portal to the DOM as an embedded view.
- * @param portal Portal to be attached.
- */
- attachTemplatePortal(portal: TemplatePortal): Map {
- let viewContainer = portal.viewContainerRef;
- let viewRef = viewContainer.createEmbeddedView(portal.templateRef);
- viewRef.detectChanges();
-
- // The method `createEmbeddedView` will add the view as a child of the viewContainer.
- // But for the DomPortalHost the view can be added everywhere in the DOM (e.g Overlay Container)
- // To move the view to the specified host element. We just re-append the existing root nodes.
- viewRef.rootNodes.forEach(rootNode => this._hostDomElement.appendChild(rootNode));
-
- this.setDisposeFn((() => {
- let index = viewContainer.indexOf(viewRef);
- if (index !== -1) {
- viewContainer.remove(index);
- }
- }));
-
- // TODO(jelbourn): Return locals from view.
- return new Map();
- }
-
- /**
- * Clears out a portal from the DOM.
- */
- dispose(): void {
- super.dispose();
- if (this._hostDomElement.parentNode != null) {
- this._hostDomElement.parentNode.removeChild(this._hostDomElement);
- }
- }
-
- /** Gets the root HTMLElement for an instantiated component. */
- private _getComponentRootNode(componentRef: ComponentRef): HTMLElement {
- return (componentRef.hostView as EmbeddedViewRef).rootNodes[0] as HTMLElement;
- }
-}
+export {DomPortalHost} from '@angular/cdk';
diff --git a/src/lib/core/portal/portal-directives.ts b/src/lib/core/portal/portal-directives.ts
index 15ef6a675332..9508c0d430f2 100644
--- a/src/lib/core/portal/portal-directives.ts
+++ b/src/lib/core/portal/portal-directives.ts
@@ -6,133 +6,4 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {
- NgModule,
- ComponentRef,
- Directive,
- TemplateRef,
- ComponentFactoryResolver,
- ViewContainerRef,
- OnDestroy,
- Input,
-} from '@angular/core';
-import {Portal, TemplatePortal, ComponentPortal, BasePortalHost} from './portal';
-
-
-/**
- * Directive version of a `TemplatePortal`. Because the directive *is* a TemplatePortal,
- * the directive instance itself can be attached to a host, enabling declarative use of portals.
- *
- * Usage:
- *
- * Hello {{name}}
- *
- */
-@Directive({
- selector: '[cdk-portal], [cdkPortal], [portal]',
- exportAs: 'cdkPortal',
-})
-export class TemplatePortalDirective extends TemplatePortal {
- constructor(templateRef: TemplateRef, viewContainerRef: ViewContainerRef) {
- super(templateRef, viewContainerRef);
- }
-}
-
-
-/**
- * Directive version of a PortalHost. Because the directive *is* a PortalHost, portals can be
- * directly attached to it, enabling declarative use.
- *
- * Usage:
- *
- */
-@Directive({
- selector: '[cdkPortalHost], [portalHost]',
- inputs: ['portal: cdkPortalHost']
-})
-export class PortalHostDirective extends BasePortalHost implements OnDestroy {
- /** The attached portal. */
- private _portal: Portal | null = null;
-
- constructor(
- private _componentFactoryResolver: ComponentFactoryResolver,
- private _viewContainerRef: ViewContainerRef) {
- super();
- }
-
- /** @deprecated */
- @Input('portalHost')
- get _deprecatedPortal() { return this.portal; }
- set _deprecatedPortal(v) { this.portal = v; }
-
- /** Portal associated with the Portal host. */
- get portal(): Portal | null {
- return this._portal;
- }
-
- set portal(portal: Portal | null) {
- if (this.hasAttached()) {
- super.detach();
- }
-
- if (portal) {
- super.attach(portal);
- }
-
- this._portal = portal;
- }
-
- ngOnDestroy() {
- super.dispose();
- this._portal = null;
- }
-
- /**
- * Attach the given ComponentPortal to this PortalHost using the ComponentFactoryResolver.
- *
- * @param portal Portal to be attached to the portal host.
- */
- attachComponentPortal(portal: ComponentPortal): ComponentRef {
- portal.setAttachedHost(this);
-
- // If the portal specifies an origin, use that as the logical location of the component
- // in the application tree. Otherwise use the location of this PortalHost.
- let viewContainerRef = portal.viewContainerRef != null ?
- portal.viewContainerRef :
- this._viewContainerRef;
-
- let componentFactory =
- this._componentFactoryResolver.resolveComponentFactory(portal.component);
- let ref = viewContainerRef.createComponent(
- componentFactory, viewContainerRef.length,
- portal.injector || viewContainerRef.parentInjector);
-
- super.setDisposeFn(() => ref.destroy());
- this._portal = portal;
-
- return ref;
- }
-
- /**
- * Attach the given TemplatePortal to this PortlHost as an embedded View.
- * @param portal Portal to be attached.
- */
- attachTemplatePortal(portal: TemplatePortal): Map {
- portal.setAttachedHost(this);
-
- this._viewContainerRef.createEmbeddedView(portal.templateRef);
- super.setDisposeFn(() => this._viewContainerRef.clear());
-
- this._portal = portal;
-
- // TODO(jelbourn): return locals from view
- return new Map();
- }
-}
-
-
-@NgModule({
- exports: [TemplatePortalDirective, PortalHostDirective],
- declarations: [TemplatePortalDirective, PortalHostDirective],
-})
-export class PortalModule {}
+export {TemplatePortalDirective, PortalHostDirective, PortalModule} from '@angular/cdk';
diff --git a/src/lib/core/portal/portal.ts b/src/lib/core/portal/portal.ts
index 1a41f6acde5a..ed84c15ecd56 100644
--- a/src/lib/core/portal/portal.ts
+++ b/src/lib/core/portal/portal.ts
@@ -6,229 +6,4 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {
- TemplateRef,
- ViewContainerRef,
- ElementRef,
- ComponentRef,
- Injector
-} from '@angular/core';
-import {
- throwNullPortalHostError,
- throwPortalAlreadyAttachedError,
- throwNoPortalAttachedError,
- throwNullPortalError,
- throwPortalHostAlreadyDisposedError,
- throwUnknownPortalTypeError
-} from './portal-errors';
-import {ComponentType} from '../overlay/generic-component-type';
-
-
-
-/**
- * A `Portal` is something that you want to render somewhere else.
- * It can be attach to / detached from a `PortalHost`.
- */
-export abstract class Portal {
- private _attachedHost: PortalHost | null;
-
- /** Attach this portal to a host. */
- attach(host: PortalHost): T {
- if (host == null) {
- throwNullPortalHostError();
- }
-
- if (host.hasAttached()) {
- throwPortalAlreadyAttachedError();
- }
-
- this._attachedHost = host;
- return host.attach(this);
- }
-
- /** Detach this portal from its host */
- detach(): void {
- let host = this._attachedHost;
-
- if (host == null) {
- throwNoPortalAttachedError();
- } else {
- this._attachedHost = null;
- host.detach();
- }
- }
-
- /** Whether this portal is attached to a host. */
- get isAttached(): boolean {
- return this._attachedHost != null;
- }
-
- /**
- * Sets the PortalHost reference without performing `attach()`. This is used directly by
- * the PortalHost when it is performing an `attach()` or `detach()`.
- */
- setAttachedHost(host: PortalHost | null) {
- this._attachedHost = host;
- }
-}
-
-
-/**
- * A `ComponentPortal` is a portal that instantiates some Component upon attachment.
- */
-export class ComponentPortal extends Portal> {
- /** The type of the component that will be instantiated for attachment. */
- component: ComponentType;
-
- /**
- * [Optional] Where the attached component should live in Angular's *logical* component tree.
- * This is different from where the component *renders*, which is determined by the PortalHost.
- * The origin is necessary when the host is outside of the Angular application context.
- */
- viewContainerRef?: ViewContainerRef | null;
-
- /** [Optional] Injector used for the instantiation of the component. */
- injector?: Injector | null;
-
- constructor(
- component: ComponentType,
- viewContainerRef?: ViewContainerRef | null,
- injector?: Injector | null) {
- super();
- this.component = component;
- this.viewContainerRef = viewContainerRef;
- this.injector = injector;
- }
-}
-
-
-/**
- * A `TemplatePortal` is a portal that represents some embedded template (TemplateRef).
- */
-export class TemplatePortal extends Portal> {
- /** The embedded template that will be used to instantiate an embedded View in the host. */
- templateRef: TemplateRef;
-
- /** Reference to the ViewContainer into which the template will be stamped out. */
- viewContainerRef: ViewContainerRef;
-
- /**
- * Additional locals for the instantiated embedded view.
- * These locals can be seen as "exports" for the template, such as how ngFor has
- * index / event / odd.
- * See https://angular.io/docs/ts/latest/api/core/EmbeddedViewRef-class.html
- */
- locals: Map = new Map();
-
- constructor(template: TemplateRef, viewContainerRef: ViewContainerRef) {
- super();
- this.templateRef = template;
- this.viewContainerRef = viewContainerRef;
- }
-
- get origin(): ElementRef {
- return this.templateRef.elementRef;
- }
-
- attach(host: PortalHost, locals?: Map): Map {
- this.locals = locals == null ? new Map() : locals;
- return super.attach(host);
- }
-
- detach(): void {
- this.locals = new Map();
- return super.detach();
- }
-}
-
-
-/**
- * A `PortalHost` is an space that can contain a single `Portal`.
- */
-export interface PortalHost {
- attach(portal: Portal): any;
-
- detach(): any;
-
- dispose(): void;
-
- hasAttached(): boolean;
-}
-
-
-/**
- * Partial implementation of PortalHost that only deals with attaching either a
- * ComponentPortal or a TemplatePortal.
- */
-export abstract class BasePortalHost implements PortalHost {
- /** The portal currently attached to the host. */
- private _attachedPortal: Portal | null;
-
- /** A function that will permanently dispose this host. */
- private _disposeFn: (() => void) | null;
-
- /** Whether this host has already been permanently disposed. */
- private _isDisposed: boolean = false;
-
- /** Whether this host has an attached portal. */
- hasAttached(): boolean {
- return !!this._attachedPortal;
- }
-
- attach(portal: Portal): any {
- if (!portal) {
- throwNullPortalError();
- }
-
- if (this.hasAttached()) {
- throwPortalAlreadyAttachedError();
- }
-
- if (this._isDisposed) {
- throwPortalHostAlreadyDisposedError();
- }
-
- if (portal instanceof ComponentPortal) {
- this._attachedPortal = portal;
- return this.attachComponentPortal(portal);
- } else if (portal instanceof TemplatePortal) {
- this._attachedPortal = portal;
- return this.attachTemplatePortal(portal);
- }
-
- throwUnknownPortalTypeError();
- }
-
- abstract attachComponentPortal(portal: ComponentPortal): ComponentRef;
-
- abstract attachTemplatePortal(portal: TemplatePortal): Map;
-
- detach(): void {
- if (this._attachedPortal) {
- this._attachedPortal.setAttachedHost(null);
- this._attachedPortal = null;
- }
-
- this._invokeDisposeFn();
- }
-
- dispose() {
- if (this.hasAttached()) {
- this.detach();
- }
-
- this._invokeDisposeFn();
- this._isDisposed = true;
- }
-
- setDisposeFn(fn: () => void) {
- this._disposeFn = fn;
- }
-
- private _invokeDisposeFn() {
- if (this._disposeFn) {
- this._disposeFn();
- this._disposeFn = null;
- }
- }
-}
+export {Portal, PortalHost, BasePortalHost, ComponentPortal, TemplatePortal} from '@angular/cdk';
diff --git a/src/lib/core/ripple/ripple.spec.ts b/src/lib/core/ripple/ripple.spec.ts
index 85b96fcaf9b3..d41b3e5a646f 100644
--- a/src/lib/core/ripple/ripple.spec.ts
+++ b/src/lib/core/ripple/ripple.spec.ts
@@ -6,6 +6,7 @@ import {dispatchMouseEvent} from '../testing/dispatch-events';
import {
MdRipple, MdRippleModule, MD_RIPPLE_GLOBAL_OPTIONS, RippleState, RippleGlobalOptions
} from './index';
+import {Platform} from '@angular/cdk';
describe('MdRipple', () => {
@@ -13,6 +14,7 @@ describe('MdRipple', () => {
let rippleTarget: HTMLElement;
let originalBodyMargin: string | null;
let viewportRuler: ViewportRuler;
+ let platform: Platform;
/** Extracts the numeric value of a pixel size string like '123px'. */
const pxStringToFloat = s => parseFloat(s) || 0;
@@ -31,8 +33,9 @@ describe('MdRipple', () => {
});
});
- beforeEach(inject([ViewportRuler], (ruler: ViewportRuler) => {
+ beforeEach(inject([ViewportRuler, Platform], (ruler: ViewportRuler, p: Platform) => {
viewportRuler = ruler;
+ platform = p;
// Set body margin to 0 during tests so it doesn't mess up position calculations.
originalBodyMargin = document.body.style.margin;
@@ -58,12 +61,10 @@ describe('MdRipple', () => {
});
it('sizes ripple to cover element', () => {
- // In the iOS simulator (BrowserStack & SauceLabs), adding the content to the
- // body causes karma's iframe for the test to stretch to fit that content once we attempt to
- // scroll the page. Setting width / height / maxWidth / maxHeight on the iframe does not
- // successfully constrain its size. As such, skip assertions in environments where the
- // window size has changed since the start of the test.
- if (window.innerWidth > startingWindowWidth || window.innerHeight > startingWindowHeight) {
+ // This test is consistently flaky on iOS (vs. Safari on desktop and all other browsers).
+ // Temporarily skip this test on iOS until we can determine the source of the flakiness.
+ // TODO(jelbourn): determine the source of flakiness here
+ if (platform.IOS) {
return;
}
diff --git a/src/lib/core/rxjs/rx-chain.ts b/src/lib/core/rxjs/rx-chain.ts
index 57b126cead24..88caa5c9b637 100644
--- a/src/lib/core/rxjs/rx-chain.ts
+++ b/src/lib/core/rxjs/rx-chain.ts
@@ -6,50 +6,4 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Observable} from 'rxjs/Observable';
-import {Subscription} from 'rxjs/Subscription';
-import {StrictRxChain} from './rx-operators';
-
-/**
- * Utility class used to chain RxJS operators.
- *
- * This class is the concrete implementation, but the type used by the user when chaining
- * is StrictRxChain. The strict chain enforces types on the operators to the same level as
- * the prototype-added equivalents.
- */
-export class RxChain {
- private constructor(private _context: Observable) { }
-
- /**
- * Starts a new chain and specifies the initial `this` value.
- * @param context Initial `this` value for the chain.
- */
- static from(context: Observable): StrictRxChain {
- return new RxChain(context);
- }
-
- /**
- * Invokes an RxJS operator as a part of the chain.
- * @param operator Operator to be invoked.
- * @param args Arguments to be passed to the operator.
- */
- call(operator: Function, ...args: any[]): RxChain {
- this._context = operator.call(this._context, ...args);
- return this;
- }
-
- /**
- * Subscribes to the result of the chain.
- * @param fn Callback to be invoked when the result emits a value.
- */
- subscribe(fn: (t: T) => void): Subscription {
- return this._context.subscribe(fn);
- }
-
- /**
- * Returns the result of the chain.
- */
- result(): Observable {
- return this._context;
- }
-}
+export {RxChain} from '@angular/cdk';
diff --git a/src/lib/core/rxjs/rx-operators.ts b/src/lib/core/rxjs/rx-operators.ts
index 28788e3e5290..9c03be71da97 100644
--- a/src/lib/core/rxjs/rx-operators.ts
+++ b/src/lib/core/rxjs/rx-operators.ts
@@ -6,111 +6,42 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Observable, ObservableInput} from 'rxjs/Observable';
-import {PartialObserver} from 'rxjs/Observer';
-import {Subscription} from 'rxjs/Subscription';
-import {IScheduler} from 'rxjs/Scheduler';
-import {_finally as _finallyOperator} from 'rxjs/operator/finally';
-import {_catch as _catchOperator} from 'rxjs/operator/catch';
-import {_do as _doOperator} from 'rxjs/operator/do';
-import {map as mapOperator} from 'rxjs/operator/map';
-import {filter as filterOperator} from 'rxjs/operator/filter';
-import {share as shareOperator} from 'rxjs/operator/share';
-import {first as firstOperator} from 'rxjs/operator/first';
-import {switchMap as switchMapOperator} from 'rxjs/operator/switchMap';
-import {startWith as startWithOperator} from 'rxjs/operator/startWith';
-import {debounceTime as debounceTimeOperator} from 'rxjs/operator/debounceTime';
-import {auditTime as auditTimeOperator} from 'rxjs/operator/auditTime';
-import {takeUntil as takeUntilOperator} from 'rxjs/operator/takeUntil';
-
-/**
- * Represents a strongly-typed chain of RxJS operators.
- *
- * We achieve strict type enforcement on the chained operators by creating types that
- * *unambiguously* match specific rxjs operators. These unambiguous types are created by
- * intersecting a "brand" to the `typeof` the existing operator. The brand (a class with a private
- * member) effectively forces nominal typing for the operators. This allows typescript to understand
- * that, for example, `filter` is *`filter`* and not, say, a map of T => boolean.
- *
- * The downside to this approach is that operators must be imported in their type-coerced form
- * rather than from the normal rxjs location.
- */
-export interface StrictRxChain {
- call(operator: mapOperatorType,
- project: (value: T, index: number) => R, thisArg?: any): StrictRxChain;
-
- call(operator: switchMapOperatorType,
- project: (value: T, index: number) => ObservableInput): StrictRxChain;
-
- call(operator: catchOperatorType,
- selector: (err: any, caught: Observable) => ObservableInput): StrictRxChain;
-
- call(operator: filterOperatorType,
- predicate: (value: T, index: number) => boolean, thisArg?: any): StrictRxChain;
-
- call(operator: shareOperatorType): StrictRxChain;
-
- call(operator: finallyOperatorType, action: () => void): StrictRxChain;
-
- call(operator: doOperatorType, next: (x: T) => void, error?:
- (e: any) => void, complete?: () => void): StrictRxChain;
-
- call(operator: doOperatorType, observer: PartialObserver): StrictRxChain;
-
- call(operator: firstOperatorType, thisArg?: any, defaultValue?: any): StrictRxChain;
-
- call(operator: firstOperatorType, predicate: (value: T) => boolean): StrictRxChain;
-
- call(operator: startWithOperatorType, ...args: any[]): StrictRxChain;
-
- call(operator: debounceTimeOperatorType, dueTime: number,
- scheduler?: IScheduler): StrictRxChain;
-
- call(operator: auditTimeOperatorType, duration: number,
- scheduler?: IScheduler): StrictRxChain;
-
- call(operator: takeUntilOperatorType, notifier: Observable): StrictRxChain;
-
- subscribe(fn: (t: T) => void): Subscription;
-
- result(): Observable;
-}
-
-export class FinallyBrand { private _; }
-export class CatchBrand { private _; }
-export class DoBrand { private _; }
-export class MapBrand { private _; }
-export class FilterBrand { private _; }
-export class ShareBrand { private _; }
-export class FirstBrand { private _; }
-export class SwitchMapBrand { private _; }
-export class StartWithBrand { private _; }
-export class DebounceTimeBrand { private _; }
-export class AuditTimeBrand { private _; }
-export class TakeUntilBrand { private _; }
-
-export type finallyOperatorType = typeof _finallyOperator & FinallyBrand;
-export type catchOperatorType = typeof _catchOperator & CatchBrand;
-export type doOperatorType = typeof _doOperator & DoBrand;
-export type mapOperatorType = typeof mapOperator & MapBrand;
-export type filterOperatorType = typeof filterOperator & FilterBrand;
-export type shareOperatorType = typeof shareOperator & ShareBrand;
-export type firstOperatorType = typeof firstOperator & FirstBrand;
-export type switchMapOperatorType = typeof switchMapOperator & SwitchMapBrand;
-export type startWithOperatorType = typeof startWithOperator & StartWithBrand;
-export type debounceTimeOperatorType = typeof debounceTimeOperator & DebounceTimeBrand;
-export type auditTimeOperatorType = typeof auditTimeOperator & AuditTimeBrand;
-export type takeUntilOperatorType = typeof takeUntilOperator & TakeUntilBrand;
-
-export const finallyOperator = _finallyOperator as typeof _finallyOperator & FinallyBrand;
-export const catchOperator = _catchOperator as typeof _catchOperator & CatchBrand;
-export const doOperator = _doOperator as typeof _doOperator & DoBrand;
-export const map = mapOperator as typeof mapOperator & MapBrand;
-export const filter = filterOperator as typeof filterOperator & FilterBrand;
-export const share = shareOperator as typeof shareOperator & ShareBrand;
-export const first = firstOperator as typeof firstOperator & FirstBrand;
-export const switchMap = switchMapOperator as typeof switchMapOperator & SwitchMapBrand;
-export const startWith = startWithOperator as typeof startWithOperator & StartWithBrand;
-export const debounceTime = debounceTimeOperator as typeof debounceTimeOperator & DebounceTimeBrand;
-export const auditTime = auditTimeOperator as typeof auditTimeOperator & AuditTimeBrand;
-export const takeUntil = takeUntilOperator as typeof takeUntilOperator & TakeUntilBrand;
+export {
+ StrictRxChain,
+ FinallyBrand,
+ CatchBrand,
+ DoBrand,
+ MapBrand,
+ FilterBrand,
+ ShareBrand,
+ FirstBrand,
+ SwitchMapBrand,
+ StartWithBrand,
+ DebounceTimeBrand,
+ AuditTimeBrand,
+ TakeUntilBrand,
+ finallyOperatorType,
+ catchOperatorType,
+ doOperatorType,
+ mapOperatorType,
+ filterOperatorType,
+ shareOperatorType,
+ firstOperatorType,
+ switchMapOperatorType,
+ startWithOperatorType,
+ debounceTimeOperatorType,
+ auditTimeOperatorType,
+ takeUntilOperatorType,
+ finallyOperator,
+ catchOperator,
+ doOperator,
+ map,
+ filter,
+ share,
+ first,
+ switchMap,
+ startWith,
+ debounceTime,
+ auditTime,
+ takeUntil,
+} from '@angular/cdk';
diff --git a/src/lib/core/style/focus-origin-monitor.spec.ts b/src/lib/core/style/focus-origin-monitor.spec.ts
index fe7726d1bcee..031659b6949f 100644
--- a/src/lib/core/style/focus-origin-monitor.spec.ts
+++ b/src/lib/core/style/focus-origin-monitor.spec.ts
@@ -209,7 +209,9 @@ describe('FocusOriginMonitor', () => {
.toBe(2, 'button should have exactly 2 focus classes');
expect(changeHandler).toHaveBeenCalledWith('program');
- buttonElement.blur();
+ // Call `blur` directly because invoking `buttonElement.blur()` does not always trigger the
+ // handler on IE11 on SauceLabs.
+ focusOriginMonitor._onBlur({} as any, buttonElement);
fixture.detectChanges();
expect(buttonElement.classList.length)
@@ -238,6 +240,7 @@ describe('FocusOriginMonitor', () => {
describe('cdkMonitorFocus', () => {
+ let focusOriginMonitor: FocusOriginMonitor;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [StyleModule],
@@ -251,6 +254,10 @@ describe('cdkMonitorFocus', () => {
TestBed.compileComponents();
}));
+ beforeEach(inject([FocusOriginMonitor], (fom: FocusOriginMonitor) => {
+ focusOriginMonitor = fom;
+ }));
+
describe('button with cdkMonitorElementFocus', () => {
let fixture: ComponentFixture;
let buttonElement: HTMLElement;
@@ -356,7 +363,9 @@ describe('cdkMonitorFocus', () => {
.toBe(2, 'button should have exactly 2 focus classes');
expect(fixture.componentInstance.focusChanged).toHaveBeenCalledWith('program');
- buttonElement.blur();
+ // Call `blur` directly because invoking `buttonElement.blur()` does not always trigger the
+ // handler on IE11 on SauceLabs.
+ focusOriginMonitor._onBlur({} as any, buttonElement);
fixture.detectChanges();
expect(buttonElement.classList.length)
diff --git a/src/lib/core/style/focus-origin-monitor.ts b/src/lib/core/style/focus-origin-monitor.ts
index 5f0430f07ed4..57affd8c803d 100644
--- a/src/lib/core/style/focus-origin-monitor.ts
+++ b/src/lib/core/style/focus-origin-monitor.ts
@@ -288,7 +288,7 @@ export class FocusOriginMonitor {
* @param event The blur event.
* @param element The monitored element.
*/
- private _onBlur(event: FocusEvent, element: HTMLElement) {
+ _onBlur(event: FocusEvent, element: HTMLElement) {
// If we are counting child-element-focus as focused, make sure that we aren't just blurring in
// order to focus another child of the monitored element.
const elementInfo = this._elementInfo.get(element);
diff --git a/src/lib/tabs/tab-label.ts b/src/lib/tabs/tab-label.ts
index 702463c3ab5e..a27ea8bee3b4 100644
--- a/src/lib/tabs/tab-label.ts
+++ b/src/lib/tabs/tab-label.ts
@@ -9,11 +9,14 @@
import {Directive, TemplateRef, ViewContainerRef} from '@angular/core';
import {TemplatePortalDirective} from '../core';
+/** Workaround for https://github.com/angular/angular/issues/17849 */
+export const _MdTabLabelBaseClass = TemplatePortalDirective;
+
/** Used to flag tab labels for use with the portal directive */
@Directive({
selector: '[md-tab-label], [mat-tab-label], [mdTabLabel], [matTabLabel]',
})
-export class MdTabLabel extends TemplatePortalDirective {
+export class MdTabLabel extends _MdTabLabelBaseClass {
constructor(templateRef: TemplateRef, viewContainerRef: ViewContainerRef) {
super(templateRef, viewContainerRef);
}