From 3ffa3cd7db808562405302342737df8bd9a712d0 Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Sat, 25 Aug 2018 16:25:31 +0200 Subject: [PATCH] fix(platform): better detect platforms + css API fixes #15165 fixes #15116 --- angular/src/providers/platform.ts | 45 +----- .../action-sheet/action-sheet.ios.scss | 2 +- core/src/components/app/app.scss | 25 +-- core/src/components/app/app.tsx | 26 +-- core/src/components/col/col.tsx | 8 +- core/src/components/footer/footer.scss | 2 +- core/src/components/hide-when/hide-when.tsx | 7 +- core/src/components/nav/nav.tsx | 14 +- core/src/components/picker/picker.scss | 2 +- core/src/components/show-when/show-when.tsx | 6 +- core/src/components/tabbar/tabbar.scss | 2 +- core/src/components/toast/toast.md.scss | 4 +- core/src/css/structure.scss | 26 +++ core/src/global/config.ts | 2 - core/src/global/ionic-global.ts | 11 +- core/src/utils/media.ts | 12 +- core/src/utils/platform.ts | 125 +++++++-------- core/src/utils/show-hide-when-utils.ts | 148 +++++------------- 18 files changed, 167 insertions(+), 300 deletions(-) diff --git a/angular/src/providers/platform.ts b/angular/src/providers/platform.ts index 35d92f8956e..6b9bc133193 100644 --- a/angular/src/providers/platform.ts +++ b/angular/src/providers/platform.ts @@ -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; /** @@ -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); - } - - /** - * 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(); + return getPlatforms(window); } - ready(): Promise { return this._readyPromise; } @@ -164,7 +128,6 @@ export class Platform { return document.dir === 'rtl'; } - /** * Get the query string parameter */ diff --git a/core/src/components/action-sheet/action-sheet.ios.scss b/core/src/components/action-sheet/action-sheet.ios.scss index d45b81f0805..59568475bf7 100644 --- a/core/src/components/action-sheet/action-sheet.ios.scss +++ b/core/src/components/action-sheet/action-sheet.ios.scss @@ -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); } diff --git a/core/src/components/app/app.scss b/core/src/components/app/app.scss index 1d02d4c87af..223bb28185b 100644 --- a/core/src/components/app/app.scss +++ b/core/src/components/app/app.scss @@ -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); - } -} diff --git a/core/src/components/app/app.tsx b/core/src/components/app/app.tsx index d58b6fa6f23..1e6ef3d1004 100644 --- a/core/src/components/app/app.tsx +++ b/core/src/components/app/app.tsx @@ -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'); +} diff --git a/core/src/components/col/col.tsx b/core/src/components/col/col.tsx index b2ec85dfb92..1c6450b03c4 100644 --- a/core/src/components/col/col.tsx +++ b/core/src/components/col/col.tsx @@ -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; diff --git a/core/src/components/footer/footer.scss b/core/src/components/footer/footer.scss index ed42f6e139c..d1bc8e9f207 100644 --- a/core/src/components/footer/footer.scss +++ b/core/src/components/footer/footer.scss @@ -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); } \ No newline at end of file diff --git a/core/src/components/hide-when/hide-when.tsx b/core/src/components/hide-when/hide-when.tsx index 25b90b95f65..b5c8f96aa7e 100644 --- a/core/src/components/hide-when/hide-when.tsx +++ b/core/src/components/hide-when/hide-when.tsx @@ -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() { diff --git a/core/src/components/nav/nav.tsx b/core/src/components/nav/nav.tsx index efb444243fa..deae4691cc1 100644 --- a/core/src/components/nav/nav.tsx +++ b/core/src/components/nav/nav.tsx @@ -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, diff --git a/core/src/components/picker/picker.scss b/core/src/components/picker/picker.scss index af3275e34b2..be314fa322d 100644 --- a/core/src/components/picker/picker.scss +++ b/core/src/components/picker/picker.scss @@ -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; diff --git a/core/src/components/show-when/show-when.tsx b/core/src/components/show-when/show-when.tsx index 6b7248826c1..87f998fbf1f 100644 --- a/core/src/components/show-when/show-when.tsx +++ b/core/src/components/show-when/show-when.tsx @@ -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() { diff --git a/core/src/components/tabbar/tabbar.scss b/core/src/components/tabbar/tabbar.scss index 244a1ab275b..3e2f5c6483f 100644 --- a/core/src/components/tabbar/tabbar.scss +++ b/core/src/components/tabbar/tabbar.scss @@ -34,7 +34,7 @@ } :host(.placement-bottom) { - padding-bottom: var(--ion-safe-area-bottom); + padding-bottom: var(--ion-safe-area-bottom, 0); } diff --git a/core/src/components/toast/toast.md.scss b/core/src/components/toast/toast.md.scss index f9c6133aa3a..03a29607148 100644 --- a/core/src/components/toast/toast.md.scss +++ b/core/src/components/toast/toast.md.scss @@ -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 { diff --git a/core/src/css/structure.scss b/core/src/css/structure.scss index 02aa2ed9d6e..2b9a3793a74 100644 --- a/core/src/css/structure.scss +++ b/core/src/css/structure.scss @@ -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); + } +} diff --git a/core/src/global/config.ts b/core/src/global/config.ts index a1ac4118edb..a5f802856c7 100644 --- a/core/src/global/config.ts +++ b/core/src/global/config.ts @@ -8,8 +8,6 @@ export interface IonicConfig { mode?: Mode; persistConfig?: boolean; - isDevice?: boolean; - statusbarPadding?: boolean; inputShims?: boolean; backButtonIcon?: string; backButtonText?: string; diff --git a/core/src/global/ionic-global.ts b/core/src/global/ionic-global.ts index 8dad425f2bd..e2034576e3e 100644 --- a/core/src/global/ionic-global.ts +++ b/core/src/global/ionic-global.ts @@ -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); diff --git a/core/src/utils/media.ts b/core/src/utils/media.ts index 7a2b2735aad..ad710602ebb 100644 --- a/core/src/utils/media.ts +++ b/core/src/utils/media.ts @@ -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; } diff --git a/core/src/utils/platform.ts b/core/src/utils/platform.ts index 90d562f865d..91ca86d475e 100644 --- a/core/src/utils/platform.ts +++ b/core/src/utils/platform.ts @@ -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; } diff --git a/core/src/utils/show-hide-when-utils.ts b/core/src/utils/show-hide-when-utils.ts index edbf110483f..b0c30b01efc 100644 --- a/core/src/utils/show-hide-when-utils.ts +++ b/core/src/utils/show-hide-when-utils.ts @@ -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)); +} + +function split(multiOptions: string): string[] { + return multiOptions.replace(/\s/g, '').split(','); } -export function getTestResult(displayWhen: DisplayWhen) { - const resultsToConsider: boolean[] = []; +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 (resultsToConsider.length === 1) { - return resultsToConsider[0]; + if (displayWhen.or) { + return results.some(r => r); + } else { + return results.every(r => r); } - 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; }