mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
fix(platform): better detect platforms + css API
fixes #15165 fixes #15116
This commit is contained in:
@@ -1,12 +1,11 @@
|
||||
import { EventEmitter, Injectable } from '@angular/core';
|
||||
import { PlatformConfig, detectPlatforms } from '@ionic/core';
|
||||
import { Platforms, getPlatforms, isPlatform } from '@ionic/core';
|
||||
import { proxyEvent } from '../util/util';
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class Platform {
|
||||
|
||||
private _platforms = detectPlatforms(window);
|
||||
private _readyPromise: Promise<string>;
|
||||
|
||||
/**
|
||||
@@ -94,20 +93,8 @@ export class Platform {
|
||||
* | electron | in Electron on a desktop device. |
|
||||
*
|
||||
*/
|
||||
is(platformName: string): boolean {
|
||||
return this._platforms.some(p => p.name === platformName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param win the window object
|
||||
* @param platforms an array of platforms (platform configs)
|
||||
* to get the appropriate platforms according to the configs provided.
|
||||
* @description
|
||||
* Detects the platforms using window and the platforms config provided.
|
||||
* Populates the platforms array so they can be used later on for platform detection.
|
||||
*/
|
||||
detectPlatforms(platforms: PlatformConfig[]) {
|
||||
return detectPlatforms(window, platforms);
|
||||
is(platformName: Platforms): boolean {
|
||||
return isPlatform(window, platformName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -130,32 +117,9 @@ export class Platform {
|
||||
* ```
|
||||
*/
|
||||
platforms(): string[] {
|
||||
return this._platforms.map(platform => platform.name);
|
||||
return getPlatforms(window);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object containing version information about all of the platforms.
|
||||
*
|
||||
* ```
|
||||
* import { Platform } from 'ionic-angular';
|
||||
*
|
||||
* @Component({...})
|
||||
* export MyPage {
|
||||
* constructor(public platform: Platform) {
|
||||
* // This will print an object containing
|
||||
* // all of the platforms and their versions
|
||||
* console.log(platform.versions());
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @returns An object containing all of the platforms and their versions.
|
||||
*/
|
||||
versions(): PlatformConfig[] {
|
||||
return this._platforms.slice();
|
||||
}
|
||||
|
||||
|
||||
ready(): Promise<string> {
|
||||
return this._readyPromise;
|
||||
}
|
||||
@@ -164,7 +128,6 @@ export class Platform {
|
||||
return document.dir === 'rtl';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the query string parameter
|
||||
*/
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
}
|
||||
|
||||
.action-sheet-wrapper {
|
||||
@include margin(var(--ion-safe-area-top), auto, var(--ion-safe-area-bottom), auto);
|
||||
@include margin(var(--ion-safe-area-top, 0), auto, var(--ion-safe-area-bottom, 0), auto);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,27 +1,4 @@
|
||||
|
||||
ion-app.is-device {
|
||||
html.plt-mobile ion-app {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
ion-app.statusbar-padding {
|
||||
--ion-safe-area-top: 20px;
|
||||
}
|
||||
|
||||
// TODO: remove once Safari 11.2 dies
|
||||
@supports (padding-top: constant(safe-area-inset-top)) {
|
||||
ion-app.statusbar-padding {
|
||||
--ion-safe-area-top: constant(safe-area-inset-top);
|
||||
--ion-safe-area-bottom: constant(safe-area-inset-bottom);
|
||||
--ion-safe-area-left: constant(safe-area-inset-left);
|
||||
--ion-safe-area-right: constant(safe-area-inset-right);
|
||||
}
|
||||
}
|
||||
|
||||
@supports (padding-top: env(safe-area-inset-top)) {
|
||||
ion-app.statusbar-padding {
|
||||
--ion-safe-area-top: env(safe-area-inset-top);
|
||||
--ion-safe-area-bottom: env(safe-area-inset-bottom);
|
||||
--ion-safe-area-left: env(safe-area-inset-left);
|
||||
--ion-safe-area-right: env(safe-area-inset-right);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Component, Element, Prop, QueueApi } from '@stencil/core';
|
||||
|
||||
import { Config } from '../../interface';
|
||||
import { isDevice, isHybrid, isStandaloneMode, needInputShims } from '../../utils/platform';
|
||||
import { isPlatform } from '../../utils/platform';
|
||||
|
||||
@Component({
|
||||
tag: 'ion-app',
|
||||
@@ -9,45 +9,31 @@ import { isDevice, isHybrid, isStandaloneMode, needInputShims } from '../../util
|
||||
})
|
||||
export class App {
|
||||
|
||||
private isDevice = false;
|
||||
|
||||
@Element() el!: HTMLElement;
|
||||
|
||||
@Prop({ context: 'window' }) win!: Window;
|
||||
@Prop({ context: 'config' }) config!: Config;
|
||||
@Prop({ context: 'queue' }) queue!: QueueApi;
|
||||
|
||||
componentWillLoad() {
|
||||
this.isDevice = this.config.getBoolean('isDevice', isDevice(this.win));
|
||||
}
|
||||
|
||||
componentDidLoad() {
|
||||
setTimeout(() => {
|
||||
importTapClick(this.win);
|
||||
importInputShims(this.win, this.config);
|
||||
importStatusTap(this.win, this.isDevice, this.queue);
|
||||
importStatusTap(this.win, this.queue);
|
||||
}, 32);
|
||||
}
|
||||
|
||||
hostData() {
|
||||
const hybrid = isHybrid(this.win);
|
||||
const isStandalone = isStandaloneMode(this.win);
|
||||
const statusbarPadding = this.config.getBoolean('statusbarPadding', hybrid || isStandalone);
|
||||
|
||||
return {
|
||||
class: {
|
||||
'ion-page': true,
|
||||
'is-device': this.isDevice,
|
||||
'is-hydrid': hybrid,
|
||||
'is-standalone': isStandalone,
|
||||
'statusbar-padding': statusbarPadding
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function importStatusTap(win: Window, device: boolean, queue: QueueApi) {
|
||||
if (device) {
|
||||
async function importStatusTap(win: Window, queue: QueueApi) {
|
||||
if (isPlatform(win, 'hybrid')) {
|
||||
(await import('../../utils/status-tap')).startStatusTap(win, queue);
|
||||
}
|
||||
}
|
||||
@@ -62,3 +48,7 @@ async function importInputShims(win: Window, config: Config) {
|
||||
(await import('../../utils/input-shims/input-shims')).startInputShims(win.document, config);
|
||||
}
|
||||
}
|
||||
|
||||
function needInputShims(win: Window) {
|
||||
return isPlatform(win, 'ios') && isPlatform(win, 'mobile');
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Component, Element, Listen, Prop } from '@stencil/core';
|
||||
|
||||
import { isMatch } from '../../utils/media';
|
||||
import { matchBreakpoint } from '../../utils/media';
|
||||
|
||||
const SUPPORTS_VARS = !!(CSS && CSS.supports && CSS.supports('--a', '0'));
|
||||
const BREAKPOINTS = ['', 'xs', 'sm', 'md', 'lg', 'xl'];
|
||||
@@ -11,7 +11,7 @@ const BREAKPOINTS = ['', 'xs', 'sm', 'md', 'lg', 'xl'];
|
||||
shadow: true
|
||||
})
|
||||
export class Col {
|
||||
[key: string]: any;
|
||||
@Prop({ context: 'window' }) win!: Window;
|
||||
|
||||
@Element() el!: HTMLStencilElement;
|
||||
|
||||
@@ -166,11 +166,11 @@ export class Col {
|
||||
let matched;
|
||||
|
||||
for (const breakpoint of BREAKPOINTS) {
|
||||
const matches = isMatch(breakpoint);
|
||||
const matches = matchBreakpoint(this.win, breakpoint);
|
||||
|
||||
// Grab the value of the property, if it exists and our
|
||||
// media query matches we return the value
|
||||
const columns = this[property + breakpoint.charAt(0).toUpperCase() + breakpoint.slice(1)];
|
||||
const columns = (this as any)[property + breakpoint.charAt(0).toUpperCase() + breakpoint.slice(1)];
|
||||
|
||||
if (matches && columns !== undefined) {
|
||||
matched = columns;
|
||||
|
||||
@@ -15,5 +15,5 @@ ion-footer {
|
||||
}
|
||||
|
||||
ion-footer ion-toolbar:last-child {
|
||||
padding-bottom: var(--ion-safe-area-bottom);
|
||||
padding-bottom: var(--ion-safe-area-bottom, 0);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Component, Element, Listen, Prop, State } from '@stencil/core';
|
||||
|
||||
import { Config, Mode } from '../../interface';
|
||||
import { DisplayWhen, PLATFORM_CONFIGS, PlatformConfig, detectPlatforms, updateTestResults } from '../../utils/show-hide-when-utils';
|
||||
import { DisplayWhen, updateTestResults } from '../../utils/show-hide-when-utils';
|
||||
|
||||
@Component({
|
||||
tag: 'ion-hide-when',
|
||||
@@ -9,8 +9,6 @@ import { DisplayWhen, PLATFORM_CONFIGS, PlatformConfig, detectPlatforms, updateT
|
||||
})
|
||||
export class HideWhen implements DisplayWhen {
|
||||
|
||||
calculatedPlatforms!: PlatformConfig[];
|
||||
|
||||
@Element() element!: HTMLElement;
|
||||
@Prop({ context: 'config' }) config!: Config;
|
||||
@Prop({ context: 'window' }) win!: Window;
|
||||
@@ -52,13 +50,12 @@ export class HideWhen implements DisplayWhen {
|
||||
@State() passesTest = false;
|
||||
|
||||
componentWillLoad() {
|
||||
this.calculatedPlatforms = detectPlatforms(this.win, PLATFORM_CONFIGS);
|
||||
this.onResize();
|
||||
}
|
||||
|
||||
@Listen('window:resize')
|
||||
onResize() {
|
||||
updateTestResults(this);
|
||||
this.passesTest = updateTestResults(this);
|
||||
}
|
||||
|
||||
hostData() {
|
||||
|
||||
@@ -797,29 +797,21 @@ export class Nav implements NavOutlet {
|
||||
|
||||
...opts
|
||||
};
|
||||
const trns = await transition(animationOpts);
|
||||
return this.transitionFinish(trns, enteringView, leavingView, opts);
|
||||
const { hasCompleted } = await transition(animationOpts);
|
||||
return this.transitionFinish(hasCompleted, enteringView, leavingView, opts);
|
||||
}
|
||||
|
||||
private transitionFinish(
|
||||
trans: Animation | null,
|
||||
hasCompleted: boolean,
|
||||
enteringView: ViewController,
|
||||
leavingView: ViewController | undefined,
|
||||
opts: NavOptions
|
||||
): NavResult {
|
||||
const hasCompleted = trans ? trans.hasCompleted : true;
|
||||
|
||||
const cleanupView = hasCompleted ? enteringView : leavingView;
|
||||
if (cleanupView) {
|
||||
this.cleanup(cleanupView);
|
||||
}
|
||||
|
||||
// this is the root transition
|
||||
// it's safe to destroy this transition
|
||||
if (trans) {
|
||||
trans.destroy();
|
||||
}
|
||||
|
||||
return {
|
||||
hasCompleted,
|
||||
requiresTransition: true,
|
||||
|
||||
@@ -56,7 +56,7 @@ ion-picker {
|
||||
|
||||
justify-content: center;
|
||||
|
||||
margin-bottom: var(--ion-safe-area-bottom);
|
||||
margin-bottom: var(--ion-safe-area-bottom, 0);
|
||||
|
||||
contain: strict;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Component, Element, Listen, Prop, State } from '@stencil/core';
|
||||
|
||||
import { Config, Mode } from '../../interface';
|
||||
import { DisplayWhen, PLATFORM_CONFIGS, PlatformConfig, detectPlatforms, updateTestResults } from '../../utils/show-hide-when-utils';
|
||||
import { DisplayWhen, updateTestResults } from '../../utils/show-hide-when-utils';
|
||||
|
||||
@Component({
|
||||
tag: 'ion-show-when',
|
||||
@@ -12,7 +12,6 @@ export class ShowWhen implements DisplayWhen {
|
||||
@Element() element?: HTMLElement;
|
||||
|
||||
@Prop({ context: 'config' }) config!: Config;
|
||||
@Prop({ context: 'platforms' }) calculatedPlatforms!: PlatformConfig[];
|
||||
@Prop({ context: 'window' }) win!: Window;
|
||||
|
||||
/**
|
||||
@@ -52,13 +51,12 @@ export class ShowWhen implements DisplayWhen {
|
||||
@State() passesTest = false;
|
||||
|
||||
componentWillLoad() {
|
||||
this.calculatedPlatforms = detectPlatforms(this.win, PLATFORM_CONFIGS);
|
||||
this.onResize();
|
||||
}
|
||||
|
||||
@Listen('window:resize')
|
||||
onResize() {
|
||||
updateTestResults(this);
|
||||
this.passesTest = updateTestResults(this);
|
||||
}
|
||||
|
||||
hostData() {
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
}
|
||||
|
||||
:host(.placement-bottom) {
|
||||
padding-bottom: var(--ion-safe-area-bottom);
|
||||
padding-bottom: var(--ion-safe-area-bottom, 0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -25,11 +25,11 @@
|
||||
}
|
||||
|
||||
.toast-md .toast-wrapper.toast-top {
|
||||
padding-top: var(--ion-safe-area-top);
|
||||
padding-top: var(--ion-safe-area-top, 0);
|
||||
}
|
||||
|
||||
.toast-md .toast-wrapper.toast-bottom {
|
||||
padding-bottom: var(--ion-safe-area-bottom);
|
||||
padding-bottom: var(--ion-safe-area-bottom, 0);
|
||||
}
|
||||
|
||||
.toast-md .toast-wrapper.toast-middle {
|
||||
|
||||
@@ -90,3 +90,29 @@ ion-toast-controller,
|
||||
.ion-page-invisible {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
html.plt-ios.plt-hybrid, html.plt-ios.plt-pwa {
|
||||
--ion-statusbar-padding: 20px;
|
||||
}
|
||||
html {
|
||||
--ion-safe-area-top: var(--ion-statusbar-padding);
|
||||
}
|
||||
|
||||
// TODO: remove once Safari 11.2 dies
|
||||
@supports (padding-top: constant(safe-area-inset-top)) {
|
||||
html {
|
||||
--ion-safe-area-top: constant(safe-area-inset-top);
|
||||
--ion-safe-area-bottom: constant(safe-area-inset-bottom);
|
||||
--ion-safe-area-left: constant(safe-area-inset-left);
|
||||
--ion-safe-area-right: constant(safe-area-inset-right);
|
||||
}
|
||||
}
|
||||
|
||||
@supports (padding-top: env(safe-area-inset-top)) {
|
||||
html {
|
||||
--ion-safe-area-top: env(safe-area-inset-top);
|
||||
--ion-safe-area-bottom: env(safe-area-inset-bottom);
|
||||
--ion-safe-area-left: env(safe-area-inset-left);
|
||||
--ion-safe-area-right: env(safe-area-inset-right);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,6 @@ export interface IonicConfig {
|
||||
mode?: Mode;
|
||||
persistConfig?: boolean;
|
||||
|
||||
isDevice?: boolean;
|
||||
statusbarPadding?: boolean;
|
||||
inputShims?: boolean;
|
||||
backButtonIcon?: string;
|
||||
backButtonText?: string;
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import 'ionicons';
|
||||
|
||||
import { configFromSession, configFromURL, saveConfig } from '../utils/config';
|
||||
import { isIOS } from '../utils/platform';
|
||||
import { isPlatform, setupPlatforms } from '../utils/platform';
|
||||
|
||||
import { Config } from './config';
|
||||
|
||||
const Ionic = (window as any)['Ionic'] = (window as any)['Ionic'] || {};
|
||||
const win = window;
|
||||
const Ionic = (win as any)['Ionic'] = (win as any)['Ionic'] || {};
|
||||
declare const Context: any;
|
||||
|
||||
// queue used to coordinate DOM reads and
|
||||
@@ -14,6 +15,10 @@ Object.defineProperty(Ionic, 'queue', {
|
||||
get: () => Context['queue']
|
||||
});
|
||||
|
||||
// Setup platforms
|
||||
setupPlatforms(win);
|
||||
Context.isPlatform = isPlatform;
|
||||
|
||||
// create the Ionic.config from raw config object (if it exists)
|
||||
// and convert Ionic.config into a ConfigApi that has a get() fn
|
||||
const configObj = {
|
||||
@@ -31,7 +36,7 @@ if (config.getBoolean('persistConfig')) {
|
||||
// which could have been set by the user, or by prerendering
|
||||
// otherwise get the mode via config settings, and fallback to md
|
||||
const documentElement = document.documentElement;
|
||||
const mode = config.get('mode', documentElement.getAttribute('mode') || (isIOS(window) ? 'ios' : 'md'));
|
||||
const mode = config.get('mode', documentElement.getAttribute('mode') || (isPlatform(win, 'ios') ? 'ios' : 'md'));
|
||||
Ionic.mode = Context.mode = mode;
|
||||
config.set('mode', mode);
|
||||
documentElement.setAttribute('mode', mode);
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// Media Query Functions
|
||||
// -----------------------------------------------------
|
||||
|
||||
export const SIZE_TO_MEDIA: any = {
|
||||
'xs': '(min-width: 0px)',
|
||||
@@ -11,15 +9,11 @@ export const SIZE_TO_MEDIA: any = {
|
||||
|
||||
// Check if the window matches the media query
|
||||
// at the breakpoint passed
|
||||
// e.g. isMatch('sm') => true if screen width exceeds 576px
|
||||
export function isMatch(breakpoint: string | undefined) {
|
||||
// e.g. matchBreakpoint('sm') => true if screen width exceeds 576px
|
||||
export function matchBreakpoint(win: Window, breakpoint: string | undefined) {
|
||||
if (!breakpoint) {
|
||||
return true;
|
||||
}
|
||||
const mediaQuery = SIZE_TO_MEDIA[breakpoint];
|
||||
if (mediaQuery && matchMedia(mediaQuery)) {
|
||||
const media = matchMedia(mediaQuery);
|
||||
return media.matches;
|
||||
}
|
||||
return false;
|
||||
return win.matchMedia(mediaQuery).matches;
|
||||
}
|
||||
|
||||
@@ -1,21 +1,59 @@
|
||||
|
||||
export function isIpad(win: Window) {
|
||||
export const PLATFORMS_MAP = {
|
||||
'ipad': isIpad,
|
||||
'iphone': isIphone,
|
||||
'ios': isIOS,
|
||||
'android': isAndroid,
|
||||
'phablet': isPhablet,
|
||||
'tablet': isTablet,
|
||||
'cordova': isCordova,
|
||||
'electron': isElectron,
|
||||
'pwa': isPWA,
|
||||
'mobile': isMobile,
|
||||
'desktop': isDesktop,
|
||||
'hybrid': isHybrid
|
||||
};
|
||||
|
||||
export type Platforms = keyof typeof PLATFORMS_MAP;
|
||||
|
||||
export function getPlatforms(win: any) {
|
||||
return win.Ionic.platforms;
|
||||
}
|
||||
|
||||
export function isPlatform(win: Window, platform: Platforms) {
|
||||
return getPlatforms(win).includes(platform);
|
||||
}
|
||||
|
||||
export function setupPlatforms(win: any) {
|
||||
let platforms: string[] = win.Ionic.platforms;
|
||||
if (platforms == null) {
|
||||
platforms = win.Ionic.platforms = detectPlatforms(win);
|
||||
const classList = win.document.documentElement.classList;
|
||||
platforms.forEach(p => classList.add(`plt-${p}`));
|
||||
}
|
||||
}
|
||||
|
||||
function detectPlatforms(win: Window): string[] {
|
||||
return Object.keys(PLATFORMS_MAP).filter(p => (PLATFORMS_MAP as any)[p](win));
|
||||
}
|
||||
|
||||
function isIpad(win: Window) {
|
||||
return testUserAgent(win, /iPad/i);
|
||||
}
|
||||
|
||||
export function isIphone(win: Window) {
|
||||
function isIphone(win: Window) {
|
||||
return testUserAgent(win, /iPhone/i);
|
||||
}
|
||||
|
||||
export function isIOS(win: Window) {
|
||||
function isIOS(win: Window) {
|
||||
return testUserAgent(win, /iPad|iPhone|iPod/i);
|
||||
}
|
||||
|
||||
export function isAndroid(win: Window) {
|
||||
return !isIOS(win);
|
||||
function isAndroid(win: Window) {
|
||||
return testUserAgent(win, /android|sink/i);
|
||||
}
|
||||
|
||||
export function isPhablet(win: Window) {
|
||||
function isPhablet(win: Window) {
|
||||
const width = win.innerWidth;
|
||||
const height = win.innerHeight;
|
||||
const smallest = Math.min(width, height);
|
||||
@@ -25,7 +63,7 @@ export function isPhablet(win: Window) {
|
||||
(largest > 620 && largest < 800);
|
||||
}
|
||||
|
||||
export function isTablet(win: Window) {
|
||||
function isTablet(win: Window) {
|
||||
const width = win.innerWidth;
|
||||
const height = win.innerHeight;
|
||||
const smallest = Math.min(width, height);
|
||||
@@ -34,88 +72,41 @@ export function isTablet(win: Window) {
|
||||
(largest > 780 && largest < 1400);
|
||||
}
|
||||
|
||||
export function isDevice(win: Window) {
|
||||
function isMobile(win: Window) {
|
||||
return matchMedia(win, '(any-pointer:coarse)');
|
||||
}
|
||||
|
||||
export function isHybrid(win: Window) {
|
||||
function isDesktop(win: Window) {
|
||||
return !isMobile(win);
|
||||
}
|
||||
|
||||
function isHybrid(win: Window) {
|
||||
return isCordova(win) || isCapacitorNative(win);
|
||||
}
|
||||
|
||||
export function isCordova(window: Window): boolean {
|
||||
function isCordova(window: Window): boolean {
|
||||
const win = window as any;
|
||||
return !!(win['cordova'] || win['phonegap'] || win['PhoneGap']);
|
||||
}
|
||||
|
||||
export function isCapacitorNative(window: Window): boolean {
|
||||
function isCapacitorNative(window: Window): boolean {
|
||||
const win = window as any;
|
||||
const capacitor = win['Capacitor'];
|
||||
return !!(capacitor && capacitor.isNative);
|
||||
}
|
||||
|
||||
export function isElectron(win: Window): boolean {
|
||||
function isElectron(win: Window): boolean {
|
||||
return testUserAgent(win, /electron/);
|
||||
}
|
||||
|
||||
export function isStandaloneMode(win: Window): boolean {
|
||||
function isPWA(win: Window): boolean {
|
||||
return win.matchMedia('(display-mode: standalone)').matches;
|
||||
}
|
||||
|
||||
export function needInputShims(win: Window) {
|
||||
return isIOS(win) && isDevice(win);
|
||||
}
|
||||
|
||||
export function testUserAgent(win: Window, expr: RegExp) {
|
||||
function testUserAgent(win: Window, expr: RegExp) {
|
||||
return expr.test(win.navigator.userAgent);
|
||||
}
|
||||
|
||||
export function matchMedia(win: Window, query: string, fallback = false): boolean {
|
||||
return win.matchMedia
|
||||
? win.matchMedia(query).matches
|
||||
: fallback;
|
||||
}
|
||||
|
||||
export interface PlatformConfig {
|
||||
name: string;
|
||||
isMatch: (win: Window) => boolean;
|
||||
}
|
||||
|
||||
export const PLATFORM_CONFIGS: PlatformConfig[] = [
|
||||
{
|
||||
name: 'ipad',
|
||||
isMatch: isIpad
|
||||
},
|
||||
{
|
||||
name: 'iphone',
|
||||
isMatch: isIphone
|
||||
},
|
||||
{
|
||||
name: 'ios',
|
||||
isMatch: isIOS
|
||||
},
|
||||
{
|
||||
name: 'android',
|
||||
isMatch: isAndroid
|
||||
},
|
||||
{
|
||||
name: 'phablet',
|
||||
isMatch: isPhablet
|
||||
},
|
||||
{
|
||||
name: 'tablet',
|
||||
isMatch: isTablet
|
||||
},
|
||||
{
|
||||
name: 'cordova',
|
||||
isMatch: isCordova
|
||||
},
|
||||
{
|
||||
name: 'electron',
|
||||
isMatch: isElectron
|
||||
}
|
||||
];
|
||||
|
||||
export function detectPlatforms(win: Window, platforms: PlatformConfig[] = PLATFORM_CONFIGS) {
|
||||
// bracket notation to ensure they're not property renamed
|
||||
return platforms.filter(p => p.isMatch(win));
|
||||
function matchMedia(win: Window, query: string): boolean {
|
||||
return win.matchMedia(query).matches;
|
||||
}
|
||||
|
||||
@@ -1,76 +1,69 @@
|
||||
import { Config, Mode } from '../interface';
|
||||
|
||||
import { SIZE_TO_MEDIA } from './media';
|
||||
import { isAndroid, isCordova, isElectron, isIOS, isIpad, isIphone, isPhablet, isTablet, matchMedia } from './platform';
|
||||
import { matchBreakpoint } from './media';
|
||||
import { isPlatform } from './platform';
|
||||
|
||||
export interface DisplayWhen {
|
||||
config: Config;
|
||||
win: Window;
|
||||
mediaQuery?: string;
|
||||
mode: Mode;
|
||||
or: boolean;
|
||||
orientation?: string;
|
||||
platform?: string;
|
||||
size?: string;
|
||||
}
|
||||
|
||||
export function updateTestResults(displayWhen: DisplayWhen) {
|
||||
displayWhen.passesTest = getTestResult(displayWhen);
|
||||
return getTestResult(displayWhen);
|
||||
}
|
||||
|
||||
export function isPlatformMatch(platforms: string[], multiPlatformString: string) {
|
||||
const userProvidedPlatforms = multiPlatformString.replace(/\s/g, '').split(',');
|
||||
for (const userProvidedPlatform of userProvidedPlatforms) {
|
||||
for (const platform of platforms) {
|
||||
if (userProvidedPlatform === platform) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
function isPlatformMatch(win: Window, multiPlatformString: string) {
|
||||
const platforms = split(multiPlatformString);
|
||||
return platforms.some(p => isPlatform(win, p as any));
|
||||
}
|
||||
|
||||
export function isModeMatch(config: Config, multiModeString: string) {
|
||||
// you can only ever be in one mode, so if an entry from the list matches, return true
|
||||
const modes = multiModeString.replace(/\s/g, '').split(',');
|
||||
function isModeMatch(config: Config, multiModeString: string) {
|
||||
const modes = split(multiModeString);
|
||||
const currentMode = config.get('mode');
|
||||
return modes.indexOf(currentMode) >= 0;
|
||||
return modes.includes(currentMode);
|
||||
}
|
||||
|
||||
export function isSizeMatch(win: Window, multiSizeString: string) {
|
||||
const sizes = multiSizeString.replace(/\s/g, '').split(',');
|
||||
for (const size of sizes) {
|
||||
const mediaQuery = SIZE_TO_MEDIA[size];
|
||||
if (mediaQuery && matchMedia(win, mediaQuery)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
function isSizeMatch(win: Window, multiSizeString: string) {
|
||||
const sizes = split(multiSizeString);
|
||||
return sizes.some(s => matchBreakpoint(win, s));
|
||||
}
|
||||
|
||||
export function getTestResult(displayWhen: DisplayWhen) {
|
||||
const resultsToConsider: boolean[] = [];
|
||||
function split(multiOptions: string): string[] {
|
||||
return multiOptions.replace(/\s/g, '').split(',');
|
||||
}
|
||||
|
||||
function getTestResult(displayWhen: DisplayWhen) {
|
||||
const results: boolean[] = [];
|
||||
if (displayWhen.mediaQuery) {
|
||||
resultsToConsider.push(matchMedia(displayWhen.win, displayWhen.mediaQuery));
|
||||
results.push(matchMedia(displayWhen.win, displayWhen.mediaQuery));
|
||||
}
|
||||
if (displayWhen.size) {
|
||||
resultsToConsider.push(isSizeMatch(displayWhen.win, displayWhen.size));
|
||||
results.push(isSizeMatch(displayWhen.win, displayWhen.size));
|
||||
}
|
||||
if (displayWhen.mode) {
|
||||
resultsToConsider.push(isModeMatch(displayWhen.config, displayWhen.mode));
|
||||
results.push(isModeMatch(displayWhen.config, displayWhen.mode));
|
||||
}
|
||||
if (displayWhen.platform) {
|
||||
const platformNames = displayWhen.calculatedPlatforms.map(platformConfig => platformConfig.name);
|
||||
resultsToConsider.push(isPlatformMatch(platformNames, displayWhen.platform));
|
||||
results.push(isPlatformMatch(displayWhen.win, displayWhen.platform));
|
||||
}
|
||||
if (displayWhen.orientation) {
|
||||
resultsToConsider.push(isOrientationMatch(displayWhen.win, displayWhen.orientation));
|
||||
results.push(isOrientationMatch(displayWhen.win, displayWhen.orientation));
|
||||
}
|
||||
|
||||
if (!resultsToConsider.length) {
|
||||
return true;
|
||||
if (displayWhen.or) {
|
||||
return results.some(r => r);
|
||||
} else {
|
||||
return results.every(r => r);
|
||||
}
|
||||
if (resultsToConsider.length === 1) {
|
||||
return resultsToConsider[0];
|
||||
}
|
||||
return resultsToConsider.reduce((prev: boolean, current: boolean) => {
|
||||
if (displayWhen.or) {
|
||||
return prev || current;
|
||||
}
|
||||
return prev && current;
|
||||
});
|
||||
}
|
||||
|
||||
export function isOrientationMatch(win: Window, orientation: string) {
|
||||
function isOrientationMatch(win: Window, orientation: string) {
|
||||
if (orientation === 'portrait') {
|
||||
return isPortrait(win);
|
||||
} else if (orientation === 'landscape') {
|
||||
@@ -80,67 +73,10 @@ export function isOrientationMatch(win: Window, orientation: string) {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isPortrait(win: Window): boolean {
|
||||
function isPortrait(win: Window): boolean {
|
||||
return matchMedia(win, '(orientation: portrait)');
|
||||
}
|
||||
|
||||
// order from most specifc to least specific
|
||||
export const PLATFORM_CONFIGS: PlatformConfig[] = [
|
||||
|
||||
{
|
||||
name: 'ipad',
|
||||
isMatch: isIpad
|
||||
},
|
||||
{
|
||||
name: 'iphone',
|
||||
isMatch: isIphone
|
||||
},
|
||||
{
|
||||
name: 'ios',
|
||||
isMatch: isIOS
|
||||
},
|
||||
{
|
||||
name: 'android',
|
||||
isMatch: isAndroid
|
||||
},
|
||||
{
|
||||
name: 'phablet',
|
||||
isMatch: isPhablet
|
||||
},
|
||||
{
|
||||
name: 'tablet',
|
||||
isMatch: isTablet
|
||||
},
|
||||
{
|
||||
name: 'cordova',
|
||||
isMatch: isCordova
|
||||
},
|
||||
{
|
||||
name: 'electron',
|
||||
isMatch: isElectron
|
||||
}
|
||||
|
||||
];
|
||||
|
||||
export interface PlatformConfig {
|
||||
name: string;
|
||||
isMatch: (win: Window) => boolean;
|
||||
}
|
||||
|
||||
export function detectPlatforms(win: Window, platforms: PlatformConfig[]) {
|
||||
// bracket notation to ensure they're not property renamed
|
||||
return platforms.filter(p => p.isMatch(win));
|
||||
}
|
||||
|
||||
export interface DisplayWhen {
|
||||
calculatedPlatforms: PlatformConfig[];
|
||||
config: Config;
|
||||
win: Window;
|
||||
mediaQuery?: string;
|
||||
mode: Mode;
|
||||
or: boolean;
|
||||
orientation?: string;
|
||||
passesTest: boolean;
|
||||
platform?: string;
|
||||
size?: string;
|
||||
function matchMedia(win: Window, query: string): boolean {
|
||||
return win.matchMedia(query).matches;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user