mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
Create screen type service to determine screen type: Web, Tablet, Handset (#6772)
This commit is contained in:
committed by
Andrew Seguin
parent
f43bf23804
commit
811e50451b
@@ -0,0 +1,35 @@
|
||||
import {coerceArray} from './array';
|
||||
|
||||
describe('coerceArray', () => {
|
||||
|
||||
it('should wrap a string in an array', () => {
|
||||
let stringVal = 'just a string';
|
||||
expect(coerceArray(stringVal)).toEqual([stringVal]);
|
||||
});
|
||||
|
||||
it('should wrap a number in an array', () => {
|
||||
let numberVal = 42;
|
||||
expect(coerceArray(numberVal)).toEqual([numberVal]);
|
||||
});
|
||||
|
||||
it('should wrap an object in an array', () => {
|
||||
let objectVal = { something: 'clever' };
|
||||
expect(coerceArray(objectVal)).toEqual([objectVal]);
|
||||
});
|
||||
|
||||
it('should wrap a null vall in an array', () => {
|
||||
let nullVal = null;
|
||||
expect(coerceArray(nullVal)).toEqual([nullVal]);
|
||||
});
|
||||
|
||||
it('should wrap an undefined value in an array', () => {
|
||||
let undefinedVal = undefined;
|
||||
expect(coerceArray(undefinedVal)).toEqual([undefinedVal]);
|
||||
});
|
||||
|
||||
it('should not wrap an array in an array', () => {
|
||||
let arrayVal = [1, 2, 3];
|
||||
expect(coerceArray(arrayVal)).toBe(arrayVal);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
/** Wraps the provided value in an array, unless the provided value is an array. */
|
||||
export function coerceArray<T>(value: T | T[]): T[] {
|
||||
return Array.isArray(value) ? value : [value];
|
||||
}
|
||||
@@ -8,3 +8,4 @@
|
||||
|
||||
export * from './boolean-property';
|
||||
export * from './number-property';
|
||||
export * from './array';
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
### BreakpointsModule
|
||||
|
||||
When including the CDK's `LayoutModule`, components can inject `BreakpointsObserver` to request
|
||||
the matching state of a CSS Media Query.
|
||||
|
||||
A set of breakpoints is provided based on the Material Design
|
||||
[breakpoint system](https://material.io/guidelines/layout/responsive-ui.html#responsive-ui-breakpoints).
|
||||
|
||||
#### Example
|
||||
```ts
|
||||
@Component({ ... })
|
||||
export class MyWidget {
|
||||
isHandset: Observable<BreakpointState>;
|
||||
|
||||
constructor(bm: BreakpointObserver) {
|
||||
bm.observe(Handset).subscribe((state: BreakpointState) => {
|
||||
if (state.matches) {
|
||||
this.makeEverythingFitOnSmallScreen();
|
||||
} else {
|
||||
this.expandEverythingToFillTheScreen();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
/**
|
||||
* @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 {LayoutModule, BreakpointObserver, BreakpointState} from './index';
|
||||
import {MediaMatcher} from './media-matcher';
|
||||
import {async, TestBed, inject} from '@angular/core/testing';
|
||||
import {Injectable} from '@angular/core';
|
||||
|
||||
describe('BreakpointObserver', () => {
|
||||
let breakpointManager: BreakpointObserver;
|
||||
let mediaMatcher: FakeMediaMatcher;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [LayoutModule],
|
||||
providers: [{provide: MediaMatcher, useClass: FakeMediaMatcher}]
|
||||
});
|
||||
}));
|
||||
|
||||
beforeEach(inject(
|
||||
[BreakpointObserver, MediaMatcher],
|
||||
(bm: BreakpointObserver, mm: FakeMediaMatcher) => {
|
||||
breakpointManager = bm;
|
||||
mediaMatcher = mm;
|
||||
}));
|
||||
|
||||
afterEach(() => {
|
||||
mediaMatcher.clear();
|
||||
});
|
||||
|
||||
it('retrieves the whether a query is currently matched', () => {
|
||||
let query = 'everything starts as true in the FakeMediaMatcher';
|
||||
expect(breakpointManager.isMatched(query)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('reuses the same MediaQueryList for matching queries', () => {
|
||||
expect(mediaMatcher.queryCount).toBe(0);
|
||||
breakpointManager.observe('query1');
|
||||
expect(mediaMatcher.queryCount).toBe(1);
|
||||
breakpointManager.observe('query1');
|
||||
expect(mediaMatcher.queryCount).toBe(1);
|
||||
breakpointManager.observe('query2');
|
||||
expect(mediaMatcher.queryCount).toBe(2);
|
||||
breakpointManager.observe('query1');
|
||||
expect(mediaMatcher.queryCount).toBe(2);
|
||||
});
|
||||
|
||||
it('accepts an array of queries', () => {
|
||||
let queries = ['1 query', '2 query', 'red query', 'blue query'];
|
||||
breakpointManager.observe(queries);
|
||||
expect(mediaMatcher.queryCount).toBe(queries.length);
|
||||
});
|
||||
|
||||
it('completes all events when the breakpoint manager is destroyed', () => {
|
||||
let firstTest = jasmine.createSpy('test1');
|
||||
breakpointManager.observe('test1').subscribe(undefined, undefined, firstTest);
|
||||
let secondTest = jasmine.createSpy('test2');
|
||||
breakpointManager.observe('test2').subscribe(undefined, undefined, secondTest);
|
||||
|
||||
expect(firstTest).not.toHaveBeenCalled();
|
||||
expect(secondTest).not.toHaveBeenCalled();
|
||||
|
||||
breakpointManager.ngOnDestroy();
|
||||
|
||||
expect(firstTest).toHaveBeenCalled();
|
||||
expect(secondTest).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('emits an event on the observable when values change', () => {
|
||||
let query = '(width: 999px)';
|
||||
let queryMatchState: boolean = false;
|
||||
breakpointManager.observe(query).subscribe((state: BreakpointState) => {
|
||||
queryMatchState = state.matches;
|
||||
});
|
||||
|
||||
async(() => {
|
||||
expect(queryMatchState).toBeTruthy();
|
||||
mediaMatcher.setMatchesQuery(query, false);
|
||||
expect(queryMatchState).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
it('emits a true matches state when the query is matched', () => {
|
||||
let query = '(width: 999px)';
|
||||
mediaMatcher.setMatchesQuery(query, true);
|
||||
expect(breakpointManager.isMatched(query)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('emits a false matches state when the query is not matched', () => {
|
||||
let query = '(width: 999px)';
|
||||
mediaMatcher.setMatchesQuery(query, false);
|
||||
expect(breakpointManager.isMatched(query)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
export class FakeMediaQueryList implements MediaQueryList {
|
||||
/** The callback for change events. */
|
||||
addListenerCallback?: (mql: MediaQueryList) => void;
|
||||
|
||||
constructor(public matches, public media) {}
|
||||
|
||||
/** Toggles the matches state and "emits" a change event. */
|
||||
setMatches(matches: boolean) {
|
||||
this.matches = matches;
|
||||
this.addListenerCallback!(this);
|
||||
}
|
||||
|
||||
/** Registers the callback method for change events. */
|
||||
addListener(callback: (mql: MediaQueryList) => void) {
|
||||
this.addListenerCallback = callback;
|
||||
}
|
||||
|
||||
/** Noop, but required for implementing MediaQueryList. */
|
||||
removeListener() {}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class FakeMediaMatcher {
|
||||
/** A map of match media queries. */
|
||||
private queries: Map<string, FakeMediaQueryList> = new Map();
|
||||
|
||||
/** The number of distinct queries created in the media matcher during a test. */
|
||||
get queryCount(): number {
|
||||
return this.queries.size;
|
||||
}
|
||||
|
||||
/** Fakes the match media response to be controlled in tests. */
|
||||
matchMedia(query: string): FakeMediaQueryList {
|
||||
let mql = new FakeMediaQueryList(true, query);
|
||||
this.queries.set(query, mql);
|
||||
return mql;
|
||||
}
|
||||
|
||||
/** Clears all queries from the map of queries. */
|
||||
clear() {
|
||||
this.queries.clear();
|
||||
}
|
||||
|
||||
/** Toggles the matching state of the provided query. */
|
||||
setMatchesQuery(query: string, matches: boolean) {
|
||||
if (this.queries.has(query)) {
|
||||
this.queries.get(query)!.setMatches(matches);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* @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 {Injectable, NgZone, OnDestroy} from '@angular/core';
|
||||
import {MediaMatcher} from './media-matcher';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
import {Subject} from 'rxjs/Subject';
|
||||
import {RxChain, map, startWith, takeUntil} from '@angular/cdk/rxjs';
|
||||
import {coerceArray} from '@angular/cdk/coercion';
|
||||
import {combineLatest} from 'rxjs/observable/combineLatest';
|
||||
import {fromEventPattern} from 'rxjs/observable/fromEventPattern';
|
||||
|
||||
/** The current state of a layout breakpoint. */
|
||||
export interface BreakpointState {
|
||||
matches: boolean;
|
||||
}
|
||||
|
||||
interface Query {
|
||||
observable: Observable<BreakpointState>;
|
||||
mql: MediaQueryList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility for checking the matching state of @media queries.
|
||||
*/
|
||||
@Injectable()
|
||||
export class BreakpointObserver implements OnDestroy {
|
||||
/** A map of all media queries currently being listened for. */
|
||||
private _queries: Map<string, Query> = new Map();
|
||||
/** A subject for all other observables to takeUntil based on. */
|
||||
private _destroySubject: Subject<{}> = new Subject();
|
||||
|
||||
constructor(private mediaMatcher: MediaMatcher, private zone: NgZone) {}
|
||||
|
||||
/** Completes the active subject, signalling to all other observables to complete. */
|
||||
ngOnDestroy() {
|
||||
this._destroySubject.next();
|
||||
this._destroySubject.complete();
|
||||
}
|
||||
|
||||
/** Whether the query currently is matched. */
|
||||
isMatched(value: string | string[]): boolean {
|
||||
let queries = coerceArray(value);
|
||||
return queries.some(mediaQuery => this._registerQuery(mediaQuery).mql.matches);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an observable of results for the given queries that will emit new results for any changes
|
||||
* in matching of the given queries.
|
||||
*/
|
||||
observe(value: string | string[]): Observable<BreakpointState> {
|
||||
let queries = coerceArray(value);
|
||||
let observables = queries.map(query => this._registerQuery(query).observable);
|
||||
|
||||
return combineLatest(observables, (a: BreakpointState, b: BreakpointState) => {
|
||||
return {
|
||||
matches: !!((a && a.matches) || (b && b.matches)),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/** Registers a specific query to be listened for. */
|
||||
private _registerQuery(query: string): Query {
|
||||
// Only set up a new MediaQueryList if it is not already being listened for.
|
||||
if (this._queries.has(query)) {
|
||||
return this._queries.get(query)!;
|
||||
}
|
||||
|
||||
let mql: MediaQueryList = this.mediaMatcher.matchMedia(query);
|
||||
// Create callback for match changes and add it is as a listener.
|
||||
let queryObservable = RxChain.from(fromEventPattern(
|
||||
// Listener callback methods are wrapped to be placed back in ngZone. Callbacks must be placed
|
||||
// back into the zone because matchMedia is only included in Zone.js by loading the
|
||||
// webapis-media-query.js file alongside the zone.js file. Additionally, some browsers do not
|
||||
// have MediaQueryList inherit from EventTarget, which causes inconsistencies in how Zone.js
|
||||
// patches it.
|
||||
(listener: MediaQueryListListener) => {
|
||||
mql.addListener((e: MediaQueryList) => this.zone.run(() => listener(e)));
|
||||
},
|
||||
(listener: MediaQueryListListener) => {
|
||||
mql.removeListener((e: MediaQueryList) => this.zone.run(() => listener(e)));
|
||||
}))
|
||||
.call(takeUntil, this._destroySubject)
|
||||
.call(startWith, mql)
|
||||
.call(map, (nextMql: MediaQueryList) => ({matches: nextMql.matches}))
|
||||
.result();
|
||||
|
||||
// Add the MediaQueryList to the set of queries.
|
||||
let output = {observable: queryObservable, mql: mql};
|
||||
this._queries.set(query, output);
|
||||
return output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
// PascalCase is being used as Breakpoints is used like an enum.
|
||||
// tslint:disable-next-line:variable-name
|
||||
export const Breakpoints = {
|
||||
Handset: '(max-width: 599px) and (orientation: portrait), ' +
|
||||
'(max-width: 959px) and (orientation: landscape)',
|
||||
Tablet: '(min-width: 600px) and (max-width: 839px) and (orientation: portrait), ' +
|
||||
'(min-width: 960px) and (max-width: 1279px) and (orientation: landscape)',
|
||||
Web: '(min-width: 840px) and (orientation: portrait), ' +
|
||||
'(min-width: 1280px) and (orientation: landscape)',
|
||||
|
||||
HandsetPortrait: '(max-width: 599px) and (orientation: portrait)',
|
||||
TabletPortrait: '(min-width: 600px) and (max-width: 839px) and (orientation: portrait)',
|
||||
WebPortrait: '(min-width: 840px) and (orientation: portrait)',
|
||||
|
||||
HandsetLandscape: '(max-width: 959px) and (orientation: landscape)',
|
||||
TabletLandscape: '(min-width: 960px) and (max-width: 1279px) and (orientation: landscape)',
|
||||
WebLandscape: '(min-width: 1280px) and (orientation: landscape)',
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* @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 './public_api';
|
||||
@@ -0,0 +1,17 @@
|
||||
### MediaMatcher
|
||||
|
||||
When including the CDK's `LayoutModule`, components can inject `MediaMatcher` to access the
|
||||
matchMedia method, if available on the platform.
|
||||
|
||||
#### Example
|
||||
```ts
|
||||
@Component({ ... })
|
||||
export class MyWidget {
|
||||
constructor(mm: MediaMatcher) {
|
||||
mm.matchMedia('(orientation: landscape)').matches ?
|
||||
this.setPortraitMode() :
|
||||
this.setPortraitMode();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* @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 {LayoutModule, BreakpointObserver} from './index';
|
||||
import {MediaMatcher} from './media-matcher';
|
||||
import {async, TestBed, inject} from '@angular/core/testing';
|
||||
import {Platform} from '@angular/cdk/platform';
|
||||
|
||||
describe('MediaMatcher', () => {
|
||||
let breakpointManager: BreakpointObserver;
|
||||
let mediaMatcher: MediaMatcher;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [LayoutModule]
|
||||
});
|
||||
}));
|
||||
|
||||
beforeEach(inject(
|
||||
[BreakpointObserver, MediaMatcher],
|
||||
(bm: BreakpointObserver, mm: MediaMatcher) => {
|
||||
breakpointManager = bm;
|
||||
mediaMatcher = mm;
|
||||
}));
|
||||
|
||||
it('correctly returns a MediaQueryList to check for matches', () => {
|
||||
expect(mediaMatcher.matchMedia('(min-width: 1px)').matches).toBeTruthy();
|
||||
expect(mediaMatcher.matchMedia('(max-width: 1px)').matches).toBeFalsy();
|
||||
});
|
||||
|
||||
it('adds css rules for provided queries when the platform is webkit, otherwise adds nothing.',
|
||||
inject([Platform], (platform: Platform) => {
|
||||
let randomWidth = Math.random();
|
||||
expect(document.head.textContent).not.toContain(randomWidth);
|
||||
mediaMatcher.matchMedia(`(width: ${randomWidth})`);
|
||||
|
||||
if (platform.WEBKIT) {
|
||||
expect(document.head.textContent).toContain(randomWidth);
|
||||
} else {
|
||||
expect(document.head.textContent).not.toContain(randomWidth);
|
||||
}
|
||||
}));
|
||||
});
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* @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 {Injectable} from '@angular/core';
|
||||
import {Platform} from '@angular/cdk/platform';
|
||||
|
||||
/**
|
||||
* Global registry for all dynamically-created, injected style tags.
|
||||
*/
|
||||
const styleElementForWebkitCompatibility: Map<string, HTMLStyleElement> = new Map();
|
||||
|
||||
/**
|
||||
* A utility for calling matchMedia queries.
|
||||
*/
|
||||
@Injectable()
|
||||
export class MediaMatcher {
|
||||
/** The internal matchMedia method to return back a MediaQueryList like object. */
|
||||
private _matchMedia: (query: string) => MediaQueryList;
|
||||
|
||||
constructor(private platform: Platform) {
|
||||
this._matchMedia = this.platform.isBrowser ?
|
||||
// matchMedia is bound to the window scope intentionally as it is an illegal invocation to
|
||||
// call it from a different scope.
|
||||
window.matchMedia.bind(window) :
|
||||
noopMatchMedia;
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms the layout engine will trigger for the selector query provided and returns the
|
||||
* MediaQueryList for the query provided.
|
||||
*/
|
||||
matchMedia(query: string): MediaQueryList {
|
||||
if (this.platform.WEBKIT) {
|
||||
createEmptyStyleRule(query);
|
||||
}
|
||||
return this._matchMedia(query);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For Webkit engines that only trigger the MediaQueryListListener when there is at least one CSS
|
||||
* selector for the respective media query.
|
||||
*/
|
||||
function createEmptyStyleRule(query: string) {
|
||||
if (!styleElementForWebkitCompatibility.has(query)) {
|
||||
try {
|
||||
const style = document.createElement('style');
|
||||
|
||||
style.setAttribute('type', 'text/css');
|
||||
if (!style.sheet) {
|
||||
const cssText = `@media ${query} {.fx-query-test{ }}`;
|
||||
style.appendChild(document.createTextNode(cssText));
|
||||
}
|
||||
|
||||
document.getElementsByTagName('head')[0].appendChild(style);
|
||||
|
||||
// Store in private global registry
|
||||
styleElementForWebkitCompatibility.set(query, style);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** No-op matchMedia replacement for non-browser platforms. */
|
||||
function noopMatchMedia(query: string): MediaQueryList {
|
||||
return {
|
||||
matches: query === 'all' || query === '',
|
||||
media: query,
|
||||
addListener: () => {},
|
||||
removeListener: () => {}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {NgModule} from '@angular/core';
|
||||
import {PlatformModule} from '@angular/cdk/platform';
|
||||
import {BreakpointObserver} from './breakpoints-observer';
|
||||
import {MediaMatcher} from './media-matcher';
|
||||
|
||||
@NgModule({
|
||||
providers: [BreakpointObserver, MediaMatcher],
|
||||
imports: [PlatformModule],
|
||||
})
|
||||
export class LayoutModule {}
|
||||
|
||||
export {BreakpointObserver, BreakpointState} from './breakpoints-observer';
|
||||
export {Breakpoints} from './breakpoints';
|
||||
export {MediaMatcher} from './media-matcher';
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "../tsconfig-build",
|
||||
"files": [
|
||||
"public_api.ts"
|
||||
],
|
||||
"angularCompilerOptions": {
|
||||
"annotateForClosureCompiler": true,
|
||||
"strictMetadataEmit": true,
|
||||
"flatModuleOutFile": "index.js",
|
||||
"flatModuleId": "@angular/cdk/layout",
|
||||
"skipTemplateCodegen": true
|
||||
}
|
||||
}
|
||||
@@ -32,4 +32,3 @@ export class DemoAppModule {
|
||||
this._appRef.bootstrap(EntryApp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@ export class DemoApp {
|
||||
{name: 'Progress Spinner', route: '/progress-spinner'},
|
||||
{name: 'Radio', route: '/radio'},
|
||||
{name: 'Ripple', route: '/ripple'},
|
||||
{name: 'Screen Type', route: '/screen-type'},
|
||||
{name: 'Select', route: '/select'},
|
||||
{name: 'Sidenav', route: '/sidenav'},
|
||||
{name: 'Slide Toggle', route: '/slide-toggle'},
|
||||
|
||||
@@ -38,6 +38,8 @@ import {SnackBarDemo} from '../snack-bar/snack-bar-demo';
|
||||
import {StepperDemo} from '../stepper/stepper-demo';
|
||||
import {PeopleDatabase} from '../table/people-database';
|
||||
import {TableDemo} from '../table/table-demo';
|
||||
import {ScreenTypeDemo} from '../screen-type/screen-type-demo';
|
||||
import {LayoutModule} from '@angular/cdk/layout';
|
||||
import {TableHeaderDemo} from '../table/table-header-demo';
|
||||
import {FoggyTabContent, RainyTabContent, SunnyTabContent, TabsDemo} from '../tabs/tabs-demo';
|
||||
import {ToolbarDemo} from '../toolbar/toolbar-demo';
|
||||
@@ -53,6 +55,7 @@ import {DEMO_APP_ROUTES} from './routes';
|
||||
ReactiveFormsModule,
|
||||
RouterModule.forChild(DEMO_APP_ROUTES),
|
||||
DemoMaterialModule,
|
||||
LayoutModule,
|
||||
],
|
||||
declarations: [
|
||||
AutocompleteDemo,
|
||||
@@ -91,6 +94,7 @@ import {DEMO_APP_ROUTES} from './routes';
|
||||
RippleDemo,
|
||||
RotiniPanel,
|
||||
ScienceJoke,
|
||||
ScreenTypeDemo,
|
||||
SelectDemo,
|
||||
SidenavDemo,
|
||||
SliderDemo,
|
||||
|
||||
@@ -27,6 +27,7 @@ import {ProgressBarDemo} from '../progress-bar/progress-bar-demo';
|
||||
import {ProgressSpinnerDemo} from '../progress-spinner/progress-spinner-demo';
|
||||
import {RadioDemo} from '../radio/radio-demo';
|
||||
import {RippleDemo} from '../ripple/ripple-demo';
|
||||
import {ScreenTypeDemo} from '../screen-type/screen-type-demo';
|
||||
import {SelectDemo} from '../select/select-demo';
|
||||
import {SidenavDemo} from '../sidenav/sidenav-demo';
|
||||
import {SlideToggleDemo} from '../slide-toggle/slide-toggle-demo';
|
||||
@@ -81,6 +82,9 @@ export const DEMO_APP_ROUTES: Routes = [
|
||||
{path: 'toolbar', component: ToolbarDemo},
|
||||
{path: 'tooltip', component: TooltipDemo},
|
||||
{path: 'typography', component: TypographyDemo},
|
||||
{path: 'expansion', component: ExpansionDemo},
|
||||
{path: 'stepper', component: StepperDemo},
|
||||
{path: 'screen-type', component: ScreenTypeDemo},
|
||||
]}
|
||||
];
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
<script src="node_modules/zone.js/dist/zone.js"></script>
|
||||
<script src="node_modules/hammerjs/hammer.min.js"></script>
|
||||
|
||||
|
||||
<script>
|
||||
System.import('system-config.js').then(function () {
|
||||
System.import('main');
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<h2>Screen Type</h2>
|
||||
|
||||
<md-grid-list cols="6" rowHeight="1:2">
|
||||
<md-grid-tile [class.active]="(isHandset | async)?.matches"
|
||||
colspan="2">
|
||||
<md-icon>smartphone</md-icon>
|
||||
<p>Handset</p>
|
||||
</md-grid-tile>
|
||||
<md-grid-tile [class.active]="(isTablet | async)?.matches"
|
||||
colspan="2">
|
||||
<md-icon>tablet_android</md-icon>
|
||||
<p>Tablet</p>
|
||||
</md-grid-tile>
|
||||
<md-grid-tile [class.active]="(isWeb | async)?.matches"
|
||||
colspan="2">
|
||||
<md-icon>laptop</md-icon>
|
||||
<p>Web</p>
|
||||
</md-grid-tile>
|
||||
<md-grid-tile [class.active]="(isPortrait | async)?.matches"
|
||||
colspan="3">
|
||||
<md-icon>stay_current_portrait</md-icon>
|
||||
<p>Portrait</p>
|
||||
</md-grid-tile>
|
||||
<md-grid-tile [class.active]="(isLandscape | async)?.matches"
|
||||
colspan="3">
|
||||
<md-icon>stay_current_landscape</md-icon>
|
||||
<p>Landscape</p>
|
||||
</md-grid-tile>
|
||||
</md-grid-list>
|
||||
@@ -0,0 +1,24 @@
|
||||
.mat-grid-list {
|
||||
max-width: 700px;
|
||||
margin: 0 auto;
|
||||
min-width: 400px;
|
||||
}
|
||||
|
||||
.mat-grid-tile {
|
||||
outline: 1px solid lightgray;
|
||||
|
||||
.mat-figure {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
max-width: 700px;
|
||||
min-width: 400px;
|
||||
text-align: center;
|
||||
margin: 15px auto;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import {Component, ViewEncapsulation} from '@angular/core';
|
||||
import {BreakpointObserver, BreakpointState, Breakpoints} from '@angular/cdk/layout';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'screen-type',
|
||||
templateUrl: 'screen-type-demo.html',
|
||||
styleUrls: ['screen-type-demo.css'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
})
|
||||
export class ScreenTypeDemo {
|
||||
isHandset: Observable<BreakpointState>;
|
||||
isTablet: Observable<BreakpointState>;
|
||||
isWeb: Observable<BreakpointState>;
|
||||
isPortrait: Observable<BreakpointState>;
|
||||
isLandscape: Observable<BreakpointState>;
|
||||
|
||||
constructor(private mqm: BreakpointObserver) {
|
||||
this.isHandset = this.mqm.observe([Breakpoints.HandsetLandscape,
|
||||
Breakpoints.HandsetPortrait]);
|
||||
this.isTablet = this.mqm.observe(Breakpoints.Tablet);
|
||||
this.isWeb = this.mqm.observe([Breakpoints.WebLandscape, Breakpoints.WebPortrait]);
|
||||
this.isPortrait = this.mqm.observe('(orientation: portrait)');
|
||||
this.isLandscape = this.mqm.observe('(orientation: landscape)');
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ System.config({
|
||||
'@angular/cdk/coercion': 'dist/packages/cdk/coercion/index.js',
|
||||
'@angular/cdk/collections': 'dist/packages/cdk/collections/index.js',
|
||||
'@angular/cdk/keycodes': 'dist/packages/cdk/keycodes/index.js',
|
||||
'@angular/cdk/layout': 'dist/bundles/cdk-layout.umd.js',
|
||||
'@angular/cdk/observers': 'dist/packages/cdk/observers/index.js',
|
||||
'@angular/cdk/overlay': 'dist/packages/cdk/overlay/index.js',
|
||||
'@angular/cdk/platform': 'dist/packages/cdk/platform/index.js',
|
||||
|
||||
@@ -56,10 +56,12 @@ export const rollupGlobals = {
|
||||
'rxjs/Subject': 'Rx',
|
||||
'rxjs/Subscription': 'Rx',
|
||||
'rxjs/Observer': 'Rx',
|
||||
'rxjs/Subscriber': 'Rx',
|
||||
'rxjs/Scheduler': 'Rx',
|
||||
'rxjs/observable/combineLatest': 'Rx.Observable',
|
||||
'rxjs/observable/forkJoin': 'Rx.Observable',
|
||||
'rxjs/observable/fromEvent': 'Rx.Observable',
|
||||
'rxjs/observable/fromEventPattern': 'Rx.Observable',
|
||||
'rxjs/observable/merge': 'Rx.Observable',
|
||||
'rxjs/observable/of': 'Rx.Observable',
|
||||
'rxjs/observable/throw': 'Rx.Observable',
|
||||
@@ -83,6 +85,7 @@ export const rollupGlobals = {
|
||||
'rxjs/add/observable/fromEvent': 'Rx.Observable',
|
||||
'rxjs/add/observable/of': 'Rx.Observable',
|
||||
'rxjs/add/observable/interval': 'Rx.Observable',
|
||||
'rxjs/add/observable/combineLatest': 'Rx.Observable',
|
||||
'rxjs/add/operator/startWith': 'Rx.Observable.prototype',
|
||||
'rxjs/add/operator/map': 'Rx.Observable.prototype',
|
||||
'rxjs/add/operator/debounceTime': 'Rx.Observable.prototype',
|
||||
|
||||
Reference in New Issue
Block a user