diff --git a/angular/src/providers/platform.ts b/angular/src/providers/platform.ts index 96d826f5fa..0be38be5e6 100644 --- a/angular/src/providers/platform.ts +++ b/angular/src/providers/platform.ts @@ -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; + 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 { - 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 { - 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 { - return versionsAsyncImpl(this); - } - ready(): Promise { - return readyImpl(this); + ready(): Promise { + 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 `` or ``. - * [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 `` element. - * We recommend the app's `index.html` file already has the correct `lang` - * attribute value set, such as ``. 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 `` 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 ``. - * [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 { - 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 { - 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 { - 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 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 { - 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; } diff --git a/core/src/components.d.ts b/core/src/components.d.ts index 830fd1df2a..c071d71061 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -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; - } - } - - 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 { diff --git a/core/src/components/app/app.tsx b/core/src/components/app/app.tsx index 21cb138138..d1e76eb596 100644 --- a/core/src/components/app/app.tsx +++ b/core/src/components/app/app.tsx @@ -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 && , + inputShims && , , - this.isDevice && , + device && , ]; } diff --git a/core/src/components/content/content.tsx b/core/src/components/content/content.tsx index 5f391d5c93..d77fc0deb4 100644 --- a/core/src/components/content/content.tsx +++ b/core/src/components/content/content.tsx @@ -130,14 +130,6 @@ export class Content { } } - hostData() { - return { - class: { - 'statusbar-padding': this.config.getBoolean('statusbarPadding') - } - }; - } - render() { this.resize(); diff --git a/core/src/components/cordova-platform/cordova-platform.tsx b/core/src/components/cordova-platform/cordova-platform.tsx deleted file mode 100644 index 3152d91fda..0000000000 --- a/core/src/components/cordova-platform/cordova-platform.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { Component, Listen, Method} from '@stencil/core'; - - -@Component({ - tag: 'ion-cordova-platform', -}) -export class CordovaPlatform { - - private readyPromise: Promise; - private readyResolve: Function; - - constructor() { - this.readyPromise = new Promise(resolve => this.readyResolve = resolve); - } - - @Method() - ready(): Promise { - 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(); - } - } -} diff --git a/core/src/components/cordova-platform/readme.md b/core/src/components/cordova-platform/readme.md deleted file mode 100644 index ac45a0e64d..0000000000 --- a/core/src/components/cordova-platform/readme.md +++ /dev/null @@ -1,19 +0,0 @@ -# ion-cordova-platform - - - - - - -## Methods - -#### exitCordovaApp() - - -#### ready() - - - ----------------------------------------------- - -*Built with [StencilJS](https://stenciljs.com/)* diff --git a/core/src/components/hide-when/hide-when.tsx b/core/src/components/hide-when/hide-when.tsx index 38db4e87de..cf67a158bf 100644 --- a/core/src/components/hide-when/hide-when.tsx +++ b/core/src/components/hide-when/hide-when.tsx @@ -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() { diff --git a/core/src/components/platform/platform.tsx b/core/src/components/platform/platform.tsx deleted file mode 100644 index 7129968d18..0000000000 --- a/core/src/components/platform/platform.tsx +++ /dev/null @@ -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() && - ]; - } - -} diff --git a/core/src/components/platform/readme.md b/core/src/components/platform/readme.md deleted file mode 100644 index 0ab913a6af..0000000000 --- a/core/src/components/platform/readme.md +++ /dev/null @@ -1,50 +0,0 @@ -# ion-platform - - - - - - -## 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/)* diff --git a/core/src/components/platform/test/basic/index.html b/core/src/components/platform/test/basic/index.html deleted file mode 100644 index 8123420789..0000000000 --- a/core/src/components/platform/test/basic/index.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - Platform Basic - - - - - - - - - - Platform - basic - - - - -

The Platforms are:

-
    - -

    The Platforms versions are:

    -
      - -

      The orientation is

      - -

      The ready event has fired:

      -
      -
      - - - - - diff --git a/core/src/components/platform/test/preview/index.html b/core/src/components/platform/test/preview/index.html deleted file mode 100644 index 3d72db3ddc..0000000000 --- a/core/src/components/platform/test/preview/index.html +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - Platform Basic - - - - - - - - - - Platform - - - - -

      The Platforms are:

      -
        - -

        The Platforms versions are:

        -
          - -

          The orientation is

          - -

          The ready event has fired:

          -
          - - -
          - - - - - diff --git a/core/src/components/show-when/show-when.tsx b/core/src/components/show-when/show-when.tsx index 5d5ba4f090..deb21a316b 100644 --- a/core/src/components/show-when/show-when.tsx +++ b/core/src/components/show-when/show-when.tsx @@ -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() { diff --git a/core/src/components/toolbar/toolbar.tsx b/core/src/components/toolbar/toolbar.tsx index 2866cd732c..69fc0e51f2 100644 --- a/core/src/components/toolbar/toolbar.tsx +++ b/core/src/components/toolbar/toolbar.tsx @@ -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 }; } diff --git a/core/src/global/config-controller.ts b/core/src/global/config-controller.ts deleted file mode 100644 index 01b25c52c0..0000000000 --- a/core/src/global/config-controller.ts +++ /dev/null @@ -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 - }; -} diff --git a/core/src/global/config.ts b/core/src/global/config.ts new file mode 100644 index 0000000000..1eaad39b7b --- /dev/null +++ b/core/src/global/config.ts @@ -0,0 +1,34 @@ + +export class Config { + + private m: Map; + + constructor(configObj: {[key: string]: any}|undefined) { + this.m = new Map(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); + } +} diff --git a/core/src/global/ionic-global.ts b/core/src/global/ionic-global.ts index a49a9dfbfa..ca99cdc5f5 100644 --- a/core/src/global/ionic-global.ts +++ b/core/src/global/ionic-global.ts @@ -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 // 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 -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); diff --git a/core/src/global/platform-configs.ts b/core/src/global/platform-configs.ts deleted file mode 100644 index 9db5afcfea..0000000000 --- a/core/src/global/platform-configs.ts +++ /dev/null @@ -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; -} diff --git a/core/src/global/platform-utils.ts b/core/src/global/platform-utils.ts deleted file mode 100644 index 333d03b5b6..0000000000 --- a/core/src/global/platform-utils.ts +++ /dev/null @@ -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'; - diff --git a/core/src/index.d.ts b/core/src/index.d.ts index 7c4e8eb108..6a64cd5dd1 100644 --- a/core/src/index.d.ts +++ b/core/src/index.d.ts @@ -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 { diff --git a/core/src/themes/ionic.mixins.scss b/core/src/themes/ionic.mixins.scss index 149f85c58e..ece5dddecf 100644 --- a/core/src/themes/ionic.mixins.scss +++ b/core/src/themes/ionic.mixins.scss @@ -706,23 +706,14 @@ // -------------------------------------------------------------------------------- @mixin toolbar-statusbar-padding($toolbar-height, $toolbar-padding, $content-padding, $statusbar-padding) { - > .toolbar.statusbar-padding:first-child { - @include padding(calc(#{$statusbar-padding} + #{$toolbar-padding}), null, null, null); - @include safe-area-padding($toolbar-padding, null, null, null); + .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); + min-height: calc(#{$toolbar-height} + #{$statusbar-padding}); + @include safe-area-sizing(min-height, safe-area-inset-top, $toolbar-height); + } } } @@ -738,21 +729,23 @@ // -------------------------------------------------------------------------------- @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 { - @include padding($statusbar-padding, null, null, null); - @include safe-area-padding(0px, null, null, null); + .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); - height: calc(#{$toolbar-height} + #{$statusbar-padding}); - @include safe-area-sizing(height, safe-area-inset-top, $toolbar-height); + height: calc(#{$toolbar-height} + #{$statusbar-padding}); + @include safe-area-sizing(height, safe-area-inset-top, $toolbar-height); - min-height: calc(#{$toolbar-height} + #{$statusbar-padding}); - @include safe-area-sizing(min-height, safe-area-inset-top, $toolbar-height) - } + 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 ion-scroll { - @include padding($statusbar-padding, null, null, null); - @include safe-area-padding(0px, null, null, null); + > ion-content:first-child ion-scroll { + @include padding($statusbar-padding, null, null, null); + @include safe-area-padding(0px, null, null, null); + } } } diff --git a/core/src/utils/platform.ts b/core/src/utils/platform.ts new file mode 100644 index 0000000000..17ed4dd367 --- /dev/null +++ b/core/src/utils/platform.ts @@ -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; +} diff --git a/core/src/utils/show-hide-when-utils.ts b/core/src/utils/show-hide-when-utils.ts index d702ab659e..a5beed877a 100644 --- a/core/src/utils/show-hide-when-utils.ts +++ b/core/src/utils/show-hide-when-utils.ts @@ -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; } + diff --git a/core/stencil.config.js b/core/stencil.config.js index f8e42220d3..f2442d034a 100644 --- a/core/stencil.config.js +++ b/core/stencil.config.js @@ -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: [