perf(platform): remove from critical path

This commit is contained in:
Manu Mtz.-Almeida
2018-04-18 23:06:56 +02:00
parent 861ce49363
commit 86a6cde4a1
23 changed files with 347 additions and 1001 deletions

View File

@ -1,115 +1,121 @@
import { PlatformConfig } from '@ionic/core';
export type DocumentDirection = 'ltr' | 'rtl';
let dir: DocumentDirection = 'ltr';
let isRtl = false;
let lang = '';
export class Platform {
_element: HTMLIonPlatformElement;
private _platforms: PlatformConfig[];
private _readyPromise: Promise<any>;
private _readyResolve: any;
constructor() {
initialize(this);
this._readyPromise = new Promise(res => { this._readyResolve = res; } );
}
/**
* @returns {boolean} returns true/false based on platform.
* @description
* Depending on the platform the user is on, `is(platformName)` will
* return `true` or `false`. Note that the same app can return `true`
* for more than one platform name. For example, an app running from
* an iPad would return `true` for the platform names: `mobile`,
* `ios`, `ipad`, and `tablet`. Additionally, if the app was running
* from Cordova then `cordova` would be true, and if it was running
* from a web browser on the iPad then `mobileweb` would be `true`.
*
* ```
* import { Platform } from 'ionic-angular';
*
* @Component({...})
* export MyPage {
* constructor(public platform: Platform) {
* if (this.platform.is('ios')) {
* // This will only print when on iOS
* console.log('I am an iOS device!');
* }
* }
* }
* ```
*
* | Platform Name | Description |
* |-----------------|------------------------------------|
* | android | on a device running Android. |
* | cordova | on a device running Cordova. |
* | core | on a desktop device. |
* | ios | on a device running iOS. |
* | ipad | on an iPad device. |
* | iphone | on an iPhone device. |
* | mobile | on a mobile device. |
* | mobileweb | in a browser on a mobile device. |
* | phablet | on a phablet device. |
* | tablet | on a tablet device. |
* | windows | on a device running Windows. |
* | electron | in Electron on a desktop device. |
*
* @param {string} platformName
*/
is(platformName: string): boolean {
return isImpl(this, platformName);
}
isAsync(platformName: string): Promise<boolean> {
return isAsyncImpl(this, platformName);
return this._platforms.some(p => p.name === platformName);
}
/**
* @returns {array} the array of platforms
* @description
* Depending on what device you are on, `platforms` can return multiple values.
* Each possible value is a hierarchy of platforms. For example, on an iPhone,
* it would return `mobile`, `ios`, and `iphone`.
*
* ```
* import { Platform } from 'ionic-angular';
*
* @Component({...})
* export MyPage {
* constructor(public platform: Platform) {
* // This will print an array of the current platforms
* console.log(this.platform.platforms());
* }
* }
* ```
*/
platforms(): string[] {
return platformsImpl(this);
}
platformsAsync(): Promise<string[]> {
return platformsAsyncImpl(this);
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 {object} An object containing all of the platforms and their versions.
*/
versions(): PlatformConfig[] {
return versionsImpl(this);
return this._platforms.slice();
}
versionsAsync(): Promise<PlatformConfig[]> {
return versionsAsyncImpl(this);
}
ready(): Promise<any> {
return readyImpl(this);
ready(): Promise<string> {
return this._readyPromise;
}
get isRTL(): boolean {
return isRtl;
return document.dir === 'rtl';
}
setDir(_dir: DocumentDirection, updateDocument: boolean) {
dir = _dir;
isRtl = dir === 'rtl';
if (updateDocument !== false) {
document.documentElement.setAttribute('dir', dir);
}
}
/**
* Returns app's language direction.
* We recommend the app's `index.html` file already has the correct `dir`
* attribute value set, such as `<html dir="ltr">` or `<html dir="rtl">`.
* [W3C: Structural markup and right-to-left text in HTML](http://www.w3.org/International/questions/qa-html-dir)
* @returns {DocumentDirection}
*/
dir(): DocumentDirection {
return dir;
}
/**
* Set the app's language and optionally the country code, which will update
* the `lang` attribute on the app's root `<html>` element.
* We recommend the app's `index.html` file already has the correct `lang`
* attribute value set, such as `<html lang="en">`. This method is useful if
* the language needs to be dynamically changed per user/session.
* [W3C: Declaring language in HTML](http://www.w3.org/International/questions/qa-html-language-declarations)
* @param {string} language Examples: `en-US`, `en-GB`, `ar`, `de`, `zh`, `es-MX`
* @param {boolean} updateDocument Specifies whether the `lang` attribute of `<html>` should be updated
*/
setLang(language: string, updateDocument: boolean) {
lang = language;
if (updateDocument !== false) {
document.documentElement.setAttribute('lang', language);
}
}
/**
* Returns app's language and optional country code.
* We recommend the app's `index.html` file already has the correct `lang`
* attribute value set, such as `<html lang="en">`.
* [W3C: Declaring language in HTML](http://www.w3.org/International/questions/qa-html-language-declarations)
* @returns {string}
*/
lang(): string {
return lang;
}
/**
* Get the query string parameter
*/
getQueryParam(key: string): string {
return getQueryParamImpl(this, key);
}
/**
* Get the query string parameter
*/
getQueryParamAsync(key: string): Promise<string> {
return getQueryParamAsyncImpl(this, key);
}
height(): number {
return window.innerHeight;
return readQueryParam(window.location.href, key);
}
isLandscape(): boolean {
@ -131,98 +137,15 @@ export class Platform {
width() {
return window.innerWidth;
}
}
export function isImpl(platform: Platform, platformName: string) {
if (platform._element && platform._element.is) {
return platform._element.is(platformName);
height(): number {
return window.innerHeight;
}
return false;
}
export function isAsyncImpl(platform: Platform, platformName: string) {
return getHydratedPlatform(platform).then(() => {
return platform._element.is(platformName);
});
}
export function platformsImpl(platform: Platform): string[] {
if (platform._element && platform._element.platforms) {
return platform._element.platforms();
}
return [];
}
export function platformsAsyncImpl(platform: Platform): Promise<string[]> {
return getHydratedPlatform(platform).then(() => {
return platform._element.platforms();
});
}
export function versionsImpl(platform: Platform): PlatformConfig[] {
if (platform._element && platform._element.versions) {
return platform._element.versions();
}
return [];
}
export function versionsAsyncImpl(platform: Platform): Promise<PlatformConfig[]> {
return getHydratedPlatform(platform).then(() => {
return platform._element.versions();
});
}
export function readyImpl(platform: Platform) {
return getHydratedPlatform(platform).then(() => {
return platform._element.ready();
});
}
export function getQueryParamImpl(platform: Platform, key: string): string {
if (platform._element && platform._element.getQueryParam) {
return platform._element.getQueryParam(key);
}
return null;
}
export function getQueryParamAsyncImpl(platform: Platform, key: string) {
return getHydratedPlatform(platform).then(() => {
return platform._element.getQueryParam(key);
});
}
export function initialize(platform: Platform) {
// first see if there is an ion-app, if there is, platform will eventually show up
// if not, add platform to the document.body
const ionApp = document.querySelector('ion-app');
if (ionApp) {
return ionApp.componentOnReady(() => {
platform._element = ionApp.querySelector('ion-platform');
});
}
// okay, there isn't an ion-app, so add <ion-platform> to the document.body
let platformElement = document.querySelector('ion-platform');
if (!platformElement) {
platformElement = document.createElement('ion-platform');
document.body.appendChild(platformElement);
}
platform._element = platformElement;
}
export function getHydratedPlatform(platform: Platform): Promise<HTMLIonPlatformElement> {
if (!platform._element) {
const ionApp = document.querySelector('ion-app');
return (ionApp as any).componentOnReady(() => {
const platformEl = ionApp.querySelector('ion-platform');
return platformEl.componentOnReady().then(() => {
platform._element = platformEl;
return platformEl;
});
});
}
return platform._element.componentOnReady();
function readQueryParam(url: string, key: string) {
key = key.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
const regex = new RegExp('[\\?&]' + key + '=([^&#]*)');
const results = regex.exec(url);
return results ? decodeURIComponent(results[1].replace(/\+/g, ' ')) : null;
}

View File

@ -38,7 +38,6 @@ import {
ModalOptions,
PickerColumn,
PickerOptions,
PlatformConfig,
PopoverOptions,
ToastOptions,
} from './index';
@ -1555,40 +1554,6 @@ declare global {
}
declare global {
namespace StencilComponents {
interface IonCordovaPlatform {
'exitCordovaApp': () => void;
'ready': () => Promise<void>;
}
}
interface HTMLIonCordovaPlatformElement extends StencilComponents.IonCordovaPlatform, HTMLStencilElement {}
var HTMLIonCordovaPlatformElement: {
prototype: HTMLIonCordovaPlatformElement;
new (): HTMLIonCordovaPlatformElement;
};
interface HTMLElementTagNameMap {
'ion-cordova-platform': HTMLIonCordovaPlatformElement;
}
interface ElementTagNameMap {
'ion-cordova-platform': HTMLIonCordovaPlatformElement;
}
namespace JSX {
interface IntrinsicElements {
'ion-cordova-platform': JSXElements.IonCordovaPlatformAttributes;
}
}
namespace JSXElements {
export interface IonCordovaPlatformAttributes extends HTMLAttributes {
}
}
}
declare global {
namespace StencilComponents {
@ -2205,12 +2170,12 @@ declare global {
namespace StencilComponents {
interface IonHideWhen {
'mediaQuery': string|undefined;
'mode': string|undefined;
'mediaQuery': string;
'mode': string;
'or': boolean;
'orientation': string|undefined;
'platform': string|undefined;
'size': string|undefined;
'orientation': string;
'platform': string;
'size': string;
}
}
@ -2233,12 +2198,12 @@ declare global {
}
namespace JSXElements {
export interface IonHideWhenAttributes extends HTMLAttributes {
'mediaQuery'?: string|undefined;
'mode'?: string|undefined;
'mediaQuery'?: string;
'mode'?: string;
'or'?: boolean;
'orientation'?: string|undefined;
'platform'?: string|undefined;
'size'?: string|undefined;
'orientation'?: string;
'platform'?: string;
'size'?: string;
}
}
}
@ -4235,54 +4200,6 @@ declare global {
}
declare global {
namespace StencilComponents {
interface IonPlatform {
'getQueryParam': (param: string) => string;
/**
* Depending on the platform the user is on, `is(platformName)` will return `true` or `false`. Note that the same app can return `true` for more than one platform name. For example, an app running from an iPad would return `true` for the platform names: `mobile`, `ios`, `ipad`, and `tablet`. Additionally, if the app was running from Cordova then `cordova` would be true, and if it was running from a web browser on the iPad then `mobileweb` would be `true`. * ``` import { Platform } from 'ionic-angular';
*/
'is': (platformName: string) => boolean;
/**
* Returns whether the device is in landscape orientation
*/
'isLandscape': () => boolean;
/**
* Returns whether the device is in portration orientation
*/
'isPortrait': () => boolean;
'platforms': () => string[];
'ready': () => any;
'versions': () => PlatformConfig[];
}
}
interface HTMLIonPlatformElement extends StencilComponents.IonPlatform, HTMLStencilElement {}
var HTMLIonPlatformElement: {
prototype: HTMLIonPlatformElement;
new (): HTMLIonPlatformElement;
};
interface HTMLElementTagNameMap {
'ion-platform': HTMLIonPlatformElement;
}
interface ElementTagNameMap {
'ion-platform': HTMLIonPlatformElement;
}
namespace JSX {
interface IntrinsicElements {
'ion-platform': JSXElements.IonPlatformAttributes;
}
}
namespace JSXElements {
export interface IonPlatformAttributes extends HTMLAttributes {
}
}
}
declare global {
namespace StencilComponents {

View File

@ -1,5 +1,6 @@
import { Component, Element, Prop } from '@stencil/core';
import { Config } from '../../index';
import { isDevice, isHybrid, needInputShims } from '../../utils/platform';
@Component({
tag: 'ion-app',
@ -12,36 +13,36 @@ import { Config } from '../../index';
}
})
export class App {
mode: string;
private isDevice = false;
private deviceHacks = false;
mode: string;
@Element() el: HTMLElement;
@Prop({ context: 'window' }) win: Window;
@Prop({ context: 'config' }) config: Config;
componentWillLoad() {
this.isDevice = this.config.getBoolean('isDevice', false);
this.deviceHacks = this.config.getBoolean('deviceHacks', false);
}
hostData() {
const hoverCSS = this.config.getBoolean('hoverCSS', false);
const hybrid = isHybrid(this.win);
const hoverCSS = this.config.getBoolean('hoverCSS', !hybrid);
const statusBar = this.config.getBoolean('statusbarPadding', hybrid);
return {
class: {
[this.mode]: true,
'statusbar-padding': statusBar,
'enable-hover': hoverCSS
}
};
}
render() {
const device = this.config.getBoolean('isDevice', isDevice(this.win));
const inputShims = this.config.getBoolean('inputShims', needInputShims(this.win));
return [
this.deviceHacks && <ion-input-shims></ion-input-shims>,
inputShims && <ion-input-shims></ion-input-shims>,
<ion-tap-click></ion-tap-click>,
this.isDevice && <ion-status-tap></ion-status-tap>,
device && <ion-status-tap></ion-status-tap>,
<slot></slot>
];
}

View File

@ -130,14 +130,6 @@ export class Content {
}
}
hostData() {
return {
class: {
'statusbar-padding': this.config.getBoolean('statusbarPadding')
}
};
}
render() {
this.resize();

View File

@ -1,35 +0,0 @@
import { Component, Listen, Method} from '@stencil/core';
@Component({
tag: 'ion-cordova-platform',
})
export class CordovaPlatform {
private readyPromise: Promise<void>;
private readyResolve: Function;
constructor() {
this.readyPromise = new Promise(resolve => this.readyResolve = resolve);
}
@Method()
ready(): Promise<void> {
return this.readyPromise;
}
@Listen('document:deviceready')
deviceReadyHandler() {
this.readyResolve();
}
@Method()
@Listen('body:exitApp')
exitCordovaApp() {
// this is lifted directly from Ionic 3
const app = (window.navigator as any).app;
if (app && app.exitApp) {
app.exitApp();
}
}
}

View File

@ -1,19 +0,0 @@
# ion-cordova-platform
<!-- Auto Generated Below -->
## Methods
#### exitCordovaApp()
#### ready()
----------------------------------------------
*Built with [StencilJS](https://stenciljs.com/)*

View File

@ -1,9 +1,8 @@
import { Component, Element, Listen, Prop, State } from '@stencil/core';
import { Config, PlatformConfig } from '../../index';
import { Config } from '../../index';
import {
DisplayWhen,
updateTestResults,
DisplayWhen, PLATFORM_CONFIGS, PlatformConfig, detectPlatforms, updateTestResults,
} from '../../utils/show-hide-when-utils';
@Component({
@ -12,22 +11,29 @@ import {
})
export class HideWhen implements DisplayWhen {
calculatedPlatforms: PlatformConfig[];
@Element() element: HTMLElement;
@Prop({ context: 'config' }) config: Config;
@Prop({ context: 'platforms' }) calculatedPlatforms: PlatformConfig[];
@Prop({ context: 'window'}) win: Window;
@Prop() orientation: string|undefined;
@Prop() mediaQuery: string|undefined;
@Prop() size: string|undefined;
@Prop() mode: string|undefined;
@Prop() platform: string|undefined;
@Prop() orientation: string;
@Prop() mediaQuery: string;
@Prop() size: string;
@Prop() mode: string;
@Prop() platform: string;
@Prop() or = false;
@State() passesTest = false;
@Listen('window:resize')
componentWillLoad() {
return updateTestResults(this);
this.calculatedPlatforms = detectPlatforms(this.win, PLATFORM_CONFIGS);
this.onResize();
}
@Listen('window:resize')
onResize() {
updateTestResults(this);
}
hostData() {

View File

@ -1,133 +0,0 @@
import { Component, Element, Method, Prop } from '@stencil/core';
import { PlatformConfig } from '../../index';
import { isCordova } from '../../global/platform-utils';
@Component({
tag: 'ion-platform',
})
export class Platform {
@Prop({ context: 'platforms' }) _platforms: PlatformConfig[];
@Prop({ context: 'readQueryParam'}) readQueryParam: (url: string, key: string) => string;
@Element() el: HTMLElement;
/**
* Depending on the platform the user is on, `is(platformName)` will
* return `true` or `false`. Note that the same app can return `true`
* for more than one platform name. For example, an app running from
* an iPad would return `true` for the platform names: `mobile`,
* `ios`, `ipad`, and `tablet`. Additionally, if the app was running
* from Cordova then `cordova` would be true, and if it was running
* from a web browser on the iPad then `mobileweb` would be `true`.
*
* *
* ```
* import { Platform } from 'ionic-angular';
*
* @Component({...})
* export MyPage {
* constructor(public platform: Platform) {
* if (this.platform.is('ios')) {
* // This will only print when on iOS
* console.log('I am an iOS device!');
* }
* }
* }
* ```
*
* | Platform Name | Description |
* |-----------------|------------------------------------|
* | android | on a device running Android. |
* | cordova | on a device running Cordova. |
* | core | on a desktop device. |
* | ios | on a device running iOS. |
* | ipad | on an iPad device. |
* | iphone | on an iPhone device. |
* | mobile | on a mobile device. |
* | mobileweb | in a browser on a mobile device. |
* | phablet | on a phablet device. |
* | tablet | on a tablet device. |
* | windows | on a device running Windows. |
*
* @param {string} platformName
*/
@Method()
is(platformName: string): boolean {
for (const platform of this._platforms) {
if (platform.name === platformName) {
return true;
}
}
return false;
}
/**
* @returns {array} the array of platforms
* @description
* Depending on what device you are on, `platforms` can return multiple values.
* Each possible value is a hierarchy of platforms. For example, on an iPhone,
* it would return `mobile`, `ios`, and `iphone`.
*
* ```
* import { Platform } from 'ionic-angular';
*
* @Component({...})
* export MyPage {
* constructor(public platform: Platform) {
* // This will print an array of the current platforms
* console.log(this.platform.platforms());
* }
* }
* ```
*/
@Method()
platforms() {
return this._platforms.map(platform => platform.name);
}
@Method()
versions() {
return this._platforms;
}
/**
* Returns whether the device is in landscape orientation
*/
@Method()
isLandscape(): boolean {
return !this.isPortrait();
}
/**
* Returns whether the device is in portration orientation
*/
@Method()
isPortrait(): boolean {
return window.matchMedia('(orientation: portrait)').matches;
}
@Method()
ready() {
// revisit this later on
if (isCordova()) {
const cordovaPlatform = this.el.querySelector('ion-cordova-plaform') as any;
return cordovaPlatform.componentOnReady().then(() => {
return cordovaPlatform.ready();
});
}
return Promise.resolve();
}
@Method()
getQueryParam(param: string): string {
return this.readQueryParam(window.location.href, param);
}
render() {
return [
isCordova() && <ion-cordova-platform/>
];
}
}

View File

@ -1,50 +0,0 @@
# ion-platform
<!-- Auto Generated Below -->
## Methods
#### getQueryParam()
#### is()
Depending on the platform the user is on, `is(platformName)` will
return `true` or `false`. Note that the same app can return `true`
for more than one platform name. For example, an app running from
an iPad would return `true` for the platform names: `mobile`,
`ios`, `ipad`, and `tablet`. Additionally, if the app was running
from Cordova then `cordova` would be true, and if it was running
from a web browser on the iPad then `mobileweb` would be `true`.
*
```
import { Platform } from 'ionic-angular';
#### isLandscape()
Returns whether the device is in landscape orientation
#### isPortrait()
Returns whether the device is in portration orientation
#### platforms()
#### ready()
#### versions()
----------------------------------------------
*Built with [StencilJS](https://stenciljs.com/)*

View File

@ -1,72 +0,0 @@
<!DOCTYPE html>
<html dir="ltr">
<head>
<meta charset="UTF-8">
<title>Platform Basic</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<script src="/dist/ionic.js"></script>
</head>
<body onload="initialize()">
<ion-platform></ion-platform>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Platform - basic</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<h2>The Platforms are:</h2>
<ul class="platform-name-list"></ul>
<h2>The Platforms versions are:</h2>
<ul class="platform-version-list"></ul>
<h2>The orientation is <span class="orientation"></span></h2>
<h2>The ready event has fired: <span class="ready"></span></h2>
</ion-content>
</ion-app>
<script>
async function initialize() {
const platform = document.querySelector('ion-platform');
await platform.componentOnReady();
const platforms = platform.platforms();
const platformListElement = document.querySelector('.platform-name-list');
platforms.forEach(platform => {
const element = document.createElement('li');
element.textContent = platform;
platformListElement.appendChild(element);
});
const platformVersionList = document.querySelector('.platform-version-list');
const versions = platform.versions();
versions.forEach(version => {
const element = document.createElement('li');
element.textContent = JSON.stringify(version);
platformVersionList.appendChild(element);
});
const orientationText = platform.isPortrait() ? 'portrait' : 'landscape';
document.querySelector('.orientation').textContent = orientationText;
const readyElement = document.querySelector('.ready');
readyElement.textContent = 'No';
// use artificial timeout to see the visual
setTimeout(() => {
platform.ready().then(() => {
readyElement.textContent = 'Yes';
});
}, 1000);
}
</script>
</body>
</html>

View File

@ -1,76 +0,0 @@
<!DOCTYPE html>
<html dir="ltr">
<head>
<meta charset="UTF-8">
<title>Platform Basic</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<script src="/dist/ionic.js"></script>
</head>
<body onload="initialize()">
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Platform</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<h2>The Platforms are:</h2>
<ul class="platform-name-list"></ul>
<h2>The Platforms versions are:</h2>
<ul class="platform-version-list"></ul>
<h2>The orientation is <span class="orientation"></span></h2>
<h2>The ready event has fired: <span class="ready"></span></h2>
</ion-content>
</ion-app>
<script>
async function initialize() {
const app = document.querySelector('ion-app');
await app.componentOnReady();
const platform = document.querySelector('ion-platform');
await platform.componentOnReady();
const platforms = platform.platforms();
const platformListElement = document.querySelector('.platform-name-list');
platforms.forEach(platform => {
const element = document.createElement('li');
element.textContent = platform;
platformListElement.appendChild(element);
});
const platformVersionList = document.querySelector('.platform-version-list');
const versions = platform.versions();
versions.forEach(version => {
const element = document.createElement('li');
element.textContent = JSON.stringify(version);
platformVersionList.appendChild(element);
});
const orientationText = platform.isPortrait() ? 'portrait' : 'landscape';
document.querySelector('.orientation').textContent = orientationText;
const readyElement = document.querySelector('.ready');
readyElement.textContent = 'No';
// use artificial timeout to see the visual
setTimeout(() => {
platform.ready().then(() => {
readyElement.textContent = 'Yes';
});
}, 1000);
}
</script>
</body>
</html>

View File

@ -1,9 +1,8 @@
import { Component, Element, Listen, Prop, State } from '@stencil/core';
import { Config, PlatformConfig } from '../../index';
import { Config } from '../../index';
import {
DisplayWhen,
updateTestResults,
DisplayWhen, PLATFORM_CONFIGS, PlatformConfig, detectPlatforms, updateTestResults,
} from '../../utils/show-hide-when-utils';
@Component({
@ -12,9 +11,11 @@ import {
})
export class ShowWhen implements DisplayWhen {
calculatedPlatforms: PlatformConfig[];
@Element() element: HTMLElement;
@Prop({ context: 'config' }) config: Config;
@Prop({ context: 'platforms' }) calculatedPlatforms: PlatformConfig[];
@Prop({ context: 'window'}) win: Window;
@Prop() orientation: string;
@Prop() mediaQuery: string;
@ -25,9 +26,14 @@ export class ShowWhen implements DisplayWhen {
@State() passesTest = false;
@Listen('window:resize')
componentWillLoad() {
return updateTestResults(this);
this.calculatedPlatforms = detectPlatforms(this.win, PLATFORM_CONFIGS);
this.onResize();
}
@Listen('window:resize')
onResize() {
updateTestResults(this);
}
hostData() {

View File

@ -42,13 +42,8 @@ export class Toolbar {
hostData() {
const themedClasses = this.translucent ? createThemedClasses(this.mode, this.color, 'toolbar-translucent') : {};
const hostClasses = {
...themedClasses,
'statusbar-padding': this.config.getBoolean('statusbarPadding')
};
return {
class: hostClasses
class: themedClasses
};
}

View File

@ -1,57 +0,0 @@
import { Config } from '../index';
import { PlatformConfig, readQueryParam } from './platform-configs';
import { isDef } from '../utils/helpers';
export function createConfigController(configObj: any, platforms: PlatformConfig[]): Config {
configObj = configObj || {};
function get(key: string, fallback?: any): any {
const queryValue = readQueryParam(window.location.href, `ionic${key}`);
if (isDef(queryValue)) {
return configObj[key] = (queryValue === 'true' ? true : queryValue === 'false' ? false : queryValue);
}
if (isDef(configObj[key])) {
return configObj[key];
}
let settings: any = null;
for (let i = 0; i < platforms.length; i++) {
settings = platforms[i]['settings'];
if (settings && isDef(settings[key])) {
return settings[key];
}
}
return fallback !== undefined ? fallback : null;
}
function getBoolean(key: string, fallback?: boolean): boolean {
const val = get(key);
if (val === null) {
return fallback !== undefined ? fallback : false;
}
if (typeof val === 'string') {
return val === 'true';
}
return !!val;
}
function getNumber(key: string, fallback?: number): number {
const val = parseFloat(get(key));
return isNaN(val) ? (fallback !== undefined ? fallback : NaN) : val;
}
function set(key: string, value: string) {
configObj[key] = value;
}
return {
get,
getBoolean,
getNumber,
set
};
}

34
core/src/global/config.ts Normal file
View File

@ -0,0 +1,34 @@
export class Config {
private m: Map<string, any>;
constructor(configObj: {[key: string]: any}|undefined) {
this.m = new Map<string, any>(configObj ? Object.entries(configObj) : undefined);
}
get(key: string, fallback?: any): any {
const value = this.m.get(key);
return (value !== undefined) ? value : fallback;
}
getBoolean(key: string, fallback = false): boolean {
const val = this.m.get(key);
if (val === undefined) {
return fallback;
}
if (typeof val === 'string') {
return val === 'true';
}
return !!val;
}
getNumber(key: string, fallback?: number): number {
const val = parseFloat(this.m.get(key));
return isNaN(val) ? (fallback !== undefined ? fallback : NaN) : val;
}
set(key: string, value: any) {
this.m.set(key, value);
}
}

View File

@ -1,10 +1,8 @@
import 'ionicons';
import { createConfigController } from './config-controller';
import { PLATFORM_CONFIGS, detectPlatforms, readQueryParam } from './platform-configs';
import { Config } from './config';
import { configFromURL, isIOS } from '../utils/platform';
const Ionic = (window as any).Ionic = (window as any).Ionic || {};
declare const Context: any;
// queue used to coordinate DOM reads and
@ -13,25 +11,18 @@ Object.defineProperty(Ionic, 'queue', {
get: () => Context.queue
});
if (!Context.platforms) {
Context.platforms = detectPlatforms(window.location.href, window.navigator.userAgent, PLATFORM_CONFIGS, 'core');
}
if (!Context.readQueryParam) {
Context.readQueryParam = readQueryParam;
}
// create the Ionic.config from raw config object (if it exists)
// and convert Ionic.config into a ConfigApi that has a get() fn
Ionic.config = Context.config = createConfigController(
Ionic.config,
Context.platforms
);
const config = Ionic.config = Context.config = new Config({
...configFromURL(window),
...Ionic.config,
});
// first see if the mode was set as an attribute on <html>
// which could have been set by the user, or by prerendering
// otherwise get the mode via config settings, and fallback to md
Ionic.mode = Context.mode = document.documentElement.getAttribute('mode') || Context.config.get('mode', 'md');
// ensure we've got the mode attribute set on <html>
document.documentElement.setAttribute('mode', Ionic.mode);
const documentElement = document.documentElement;
const mode = config.get('mode', documentElement.getAttribute('mode') || (isIOS(window) ? 'ios' : 'md'));
Ionic.mode = Context.mode = mode;
config.set('mode', mode);
documentElement.setAttribute('mode', Ionic.mode);

View File

@ -1,153 +0,0 @@
import { isCordova, isElectron, } from './platform-utils';
import {
ANDROID,
CORDOVA,
CORE,
ELECTRON,
IOS,
IPAD,
IPHONE,
MOBILE,
PHABLET,
TABLET,
WINDOWS_PHONE,
} from './platform-utils';
const width = window.innerWidth;
const height = window.innerHeight;
// order from most specifc to least specific
export const PLATFORM_CONFIGS: PlatformConfig[] = [
{
name: IPAD,
settings: {
keyboardHeight: 500,
},
isMatch: (url, userAgent) => isPlatformMatch(url, userAgent, IPAD, [IPAD], WINDOWS_PHONE)
},
{
name: IPHONE,
isMatch: (url, userAgent) => isPlatformMatch(url, userAgent, IPHONE, [IPHONE], WINDOWS_PHONE)
},
{
name: IOS,
settings: {
mode: 'ios',
tabsHighlight: false,
statusbarPadding: isCordova(),
keyboardHeight: 250,
isDevice: true,
deviceHacks: true,
},
isMatch: (url, userAgent) => isPlatformMatch(url, userAgent, IOS, [IPHONE, IPAD, 'ipod'], WINDOWS_PHONE)
},
{
name: ANDROID,
settings: {
mode: 'md',
isDevice: true,
keyboardHeight: 300,
},
isMatch: (url, userAgent) => isPlatformMatch(url, userAgent, ANDROID, [ANDROID, 'silk'], WINDOWS_PHONE)
},
{
name: CORE,
settings: {
mode: 'md'
}
},
{
name: PHABLET,
isMatch: () => {
const smallest = Math.min(width, height);
const largest = Math.max(width, height);
return (smallest > 390 && smallest < 520) &&
(largest > 620 && largest < 800);
}
},
{
name: MOBILE
},
{
name: TABLET,
isMatch: () => {
const smallest = Math.min(width, height);
const largest = Math.max(width, height);
return (smallest > 460 && smallest < 820) &&
(largest > 780 && largest < 1400);
}
},
{
name: CORDOVA,
isMatch: () => {
return isCordova();
}
},
{
name: ELECTRON,
isMatch: () => {
return isElectron();
}
}
];
export function detectPlatforms(url: string, userAgent: string, platforms: PlatformConfig[], defaultPlatform: string) {
// bracket notation to ensure they're not property renamed
let validPlatforms = platforms.filter(p => p.isMatch && p.isMatch(url, userAgent));
if (!validPlatforms.length) {
validPlatforms = platforms.filter(p => p.name === defaultPlatform);
}
return validPlatforms;
}
export function isPlatformMatch(url: string, userAgent: string, platformName: string, userAgentAtLeastHas: string[], userAgentMustNotHave: string[]) {
const queryValue = readQueryParam(url, 'ionicplatform');
if (queryValue) {
return queryValue === platformName;
}
if (userAgent) {
userAgent = userAgent.toLowerCase();
for (let i = 0; i < userAgentAtLeastHas.length; i++) {
if (userAgent.indexOf(userAgentAtLeastHas[i]) > -1) {
for (let j = 0; j < userAgentMustNotHave.length; j++) {
if (userAgent.indexOf(userAgentMustNotHave[j]) > -1) {
return false;
}
}
return true;
}
}
}
return false;
}
export function readQueryParam(url: string, key: string) {
key = key.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
const regex = new RegExp('[\\?&]' + key + '=([^&#]*)');
const results = regex.exec(url);
return results ? decodeURIComponent(results[1].replace(/\+/g, ' ')) : null;
}
export interface PlatformConfig {
name: string;
isMatch?: {(url: string, userAgent: string): boolean};
settings?: any;
}

View File

@ -1,35 +0,0 @@
export function isCordova(): boolean {
const win = window as any;
return !!(win[CORDOVA] || win[PHONEGAP_CAMELCASE] || win[PHONEGAP] || win[CAPACITOR]);
}
export function isElectron(): boolean {
return testUserAgent(getUserAgent(), ELECTRON);
}
export function getUserAgent(): string {
return window.navigator.userAgent;
}
export function testUserAgent(userAgent: string, expression: string): boolean {
return userAgent ? userAgent.indexOf(expression) >= 0 : false;
}
export const ANDROID = 'android';
export const CORDOVA = 'cordova';
export const CORE = 'core';
export const ELECTRON = 'electron';
export const IOS = 'ios';
export const IPAD = 'ipad';
export const IPHONE = 'iphone';
export const MOBILE = 'mobile';
export const MOBILE_WEB = 'mobileweb';
export const PHABLET = 'phablet';
export const TABLET = 'tablet';
export const WINDOWS_PHONE = ['windows phone'];
export const PHONEGAP = 'phonegap';
export const PHONEGAP_CAMELCASE = 'PhoneGap';
export const CAPACITOR = 'Capacitor';

12
core/src/index.d.ts vendored
View File

@ -101,26 +101,18 @@ export { ToastController } from './components/toast-controller/toast-controller'
export { Toggle } from './components/toggle/toggle';
export { Toolbar } from './components/toolbar/toolbar';
export { PlatformConfig } from './global/platform-configs';
// export all of the component declarations that are dynamically created
export * from './components';
export { Config } from './global/config';
export { QueueController, RafCallback } from './global/queue-controller';
export { FrameworkDelegate } from './utils/framework-delegate';
export { OverlayEventDetail } from './utils/overlays';
export * from './utils/platform';
export * from './utils/transition';
export type ComponentRef = Function | HTMLElement | string;
export type ComponentProps = {[key: string]: any};
export interface Config {
get: (key: string, fallback?: any) => any;
getBoolean: (key: string, fallback?: boolean) => boolean;
getNumber: (key: string, fallback?: number) => number;
set: (key: string, value: any) => void;
}
export type CssClassMap = { [className: string]: boolean };
export interface BaseInputComponent {

View File

@ -706,23 +706,14 @@
// --------------------------------------------------------------------------------
@mixin toolbar-statusbar-padding($toolbar-height, $toolbar-padding, $content-padding, $statusbar-padding) {
> .toolbar.statusbar-padding:first-child {
.statusbar-padding {
> .toolbar:first-child {
@include padding(calc(#{$statusbar-padding} + #{$toolbar-padding}), null, null, null);
@include safe-area-padding($toolbar-padding, null, null, null);
min-height: calc(#{$toolbar-height} + #{$statusbar-padding});
@include safe-area-sizing(min-height, safe-area-inset-top, $toolbar-height);
}
> ion-content.statusbar-padding:first-child .scroll-content {
@include padding($statusbar-padding, null, null, null);
@include safe-area-padding(0px, null, null, null);
}
> ion-content.statusbar-padding:first-child[padding] .scroll-content,
> ion-content.statusbar-padding:first-child[padding-top] .scroll-content {
@include padding(calc(#{$content-padding} + #{$statusbar-padding}), null, null, null);
@include safe-area-padding(0px, null, null, null);
}
}
@ -738,8 +729,9 @@
// --------------------------------------------------------------------------------
@mixin toolbar-title-statusbar-padding($toolbar-height, $toolbar-padding, $content-padding, $statusbar-padding) {
> .toolbar.statusbar-padding:first-child ion-segment,
> .toolbar.statusbar-padding:first-child ion-title {
.statusbar-padding {
> .toolbar:first-child ion-segment,
> .toolbar:first-child ion-title {
@include padding($statusbar-padding, null, null, null);
@include safe-area-padding(0px, null, null, null);
@ -750,9 +742,10 @@
@include safe-area-sizing(min-height, safe-area-inset-top, $toolbar-height)
}
> ion-content.statusbar-padding:first-child ion-scroll {
> ion-content:first-child ion-scroll {
@include padding($statusbar-padding, null, null, null);
@include safe-area-padding(0px, null, null, null);
}
}
}

View File

@ -0,0 +1,78 @@
export function isIpad(win: Window) {
return testUserAgent(win, /iPad/i);
}
export function isIphone(win: Window) {
return testUserAgent(win, /iPhone/i);
}
export function isIOS(win: Window) {
return testUserAgent(win, /iPad|iPhone|iPod/i);
}
export function isAndroid(win: Window) {
return !isIOS(win);
}
export function isPhablet(win: Window) {
const width = win.innerWidth;
const height = win.innerHeight;
const smallest = Math.min(width, height);
const largest = Math.max(width, height);
return (smallest > 390 && smallest < 520) &&
(largest > 620 && largest < 800);
}
export function isTablet(win: Window) {
const width = win.innerWidth;
const height = win.innerHeight;
const smallest = Math.min(width, height);
const largest = Math.max(width, height);
return (smallest > 460 && smallest < 820) &&
(largest > 780 && largest < 1400);
}
export function isDevice(win: Window) {
return win.matchMedia('(any-pointer:coarse)').matches;
}
export function isHybrid(win: Window) {
return isCordova(win) || isCapacitor(win);
}
export function isCordova(window: Window): boolean {
const win = window as any;
return !!(win['cordova'] || win['phonegap'] || win['PhoneGap']);
}
export function isCapacitor(window: Window): boolean {
const win = window as any;
return !!(win['Capacitor']);
}
export function isElectron(win: Window): boolean {
return testUserAgent(win, /electron/);
}
export function needInputShims(win: Window) {
return isIOS(win) && isDevice(win);
}
export function testUserAgent(win: Window, expr: RegExp) {
return expr.test(win.navigator.userAgent);
}
export function configFromURL(win: Window) {
const config: any = {};
win.location.search.slice(1)
.split('&')
.filter(entryText => entryText.startsWith('ionic:'))
.map(entryText => entryText.split('='))
.forEach(entry => {
config[entry[0].slice(6)] = decodeURIComponent(entry[1]);
});
return config;
}

View File

@ -1,4 +1,5 @@
import { Config, PlatformConfig } from '../index';
import { isAndroid, isCordova, isElectron, isIOS, isIpad, isIphone, isPhablet, isTablet } from './platform';
import { Config } from '..';
export function updateTestResults(displayWhen: DisplayWhen) {
displayWhen.passesTest = getTestResult(displayWhen);
@ -23,7 +24,6 @@ export function isModeMatch(config: Config, multiModeString: string) {
return modes.indexOf(currentMode) >= 0;
}
export function isMediaQueryMatch(mediaQuery: string) {
return window.matchMedia(mediaQuery).matches;
}
@ -94,6 +94,54 @@ const SIZE_TO_MEDIA: any = {
'xl': '(min-width: 1200px)',
};
// 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;
@ -105,3 +153,4 @@ export interface DisplayWhen {
platform: string|undefined;
size: string|undefined;
}

View File

@ -31,7 +31,6 @@ exports.config = {
{ components: ['ion-tabs', 'ion-tab', 'ion-tabbar', 'ion-tab-button'] },
{ components: ['ion-toast', 'ion-toast-controller'] },
{ components: ['ion-status-tap'] },
{ components: ['ion-platform', 'ion-cordova-platform'] },
{ components: ['ion-hide-when', 'ion-show-when'] },
],
plugins: [