diff --git a/react/package.json b/react/package.json index 622c49e412..a4c1170f60 100644 --- a/react/package.json +++ b/react/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/react", - "version": "0.0.6-3", + "version": "0.0.6", "description": "React specific wrapper for @ionic/core", "keywords": [ "ionic", @@ -36,14 +36,15 @@ "dist/" ], "dependencies": { - "@ionic/core": "^4.6.0-dev.201906192117.6727cfc", + "@ionic/core": "^4.6.0", "tslib": "^1.10.0" }, "peerDependencies": { "react": "^16.8.6", "react-dom": "^16.8.6", "react-router": "^5.0.1", - "react-router-dom": "^5.0.1" + "react-router-dom": "^5.0.1", + "ionicons": "^4.5.10-2" }, "devDependencies": { "@trust/webcrypto": "^0.9.2", @@ -53,6 +54,7 @@ "@types/react-dom": "^16.8.4", "@types/react-router": "^4.4.4", "@types/react-router-dom": "^4.3.1", + "ionicons": "^4.6.0", "jest": "^24.8.0", "jest-dom": "^3.4.0", "np": "^5.0.1", diff --git a/react/src/components/IonApp.tsx b/react/src/components/IonApp.tsx new file mode 100644 index 0000000000..cdc19f2259 --- /dev/null +++ b/react/src/components/IonApp.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { IonAppInner } from './proxies'; +import { IonicConfig } from '@ionic/core'; +import { IonicContext, IonicContextState } from './utils/IonicContext'; +import { Platform } from './utils/platform'; +import { getConfig, setConfig } from './utils/config'; + +interface IonAppProps { + initialConfig?: IonicConfig; +} + +interface IonAppState extends IonicContextState { }; + +export class IonApp extends React.Component { + + constructor(props: IonAppProps) { + super(props); + + const ionicPlatform = new Platform(); + this.state = { + getConfig: getConfig, + setConfig: setConfig, + platform: ionicPlatform + } + } + + render() { + return ( + + {this.props.children} + + ); + } + +}; + +export default IonAppProps; diff --git a/react/src/components/createComponent.tsx b/react/src/components/createComponent.tsx index 85c77cb3e5..5b0ab3cc8e 100644 --- a/react/src/components/createComponent.tsx +++ b/react/src/components/createComponent.tsx @@ -2,7 +2,7 @@ import React from 'react'; import ReactDom from 'react-dom'; import { dashToPascalCase, attachEventProps } from './utils'; -export function createReactComponent(tagName: string) { +export function createReactComponent(tagName: string, attributeValues: string[] = []) { const displayName = dashToPascalCase(tagName); type IonicReactInternalProps = { @@ -17,11 +17,9 @@ export function createReactComponent(tagName: string) { } class ReactComponent extends React.Component { - componentRef: React.RefObject; constructor(props: PropType & IonicReactInternalProps) { super(props); - this.componentRef = React.createRef(); } static get displayName() { @@ -40,10 +38,17 @@ export function createReactComponent(tagName: string) { render() { const { children, forwardedRef, ...cProps } = this.props; + const propsWithoutAttributeValues = Object.keys(cProps).reduce((oldValue, key) => { + if(attributeValues.indexOf(key) === -1) { + (oldValue as any)[key] = (cProps as any)[key]; + } + return oldValue; + }, {}); + return React.createElement( tagName, { - ...cProps, + ...propsWithoutAttributeValues, ref: forwardedRef }, children diff --git a/react/src/components/createControllerComponent.tsx b/react/src/components/createControllerComponent.tsx index e986b897f7..24734b3fc2 100644 --- a/react/src/components/createControllerComponent.tsx +++ b/react/src/components/createControllerComponent.tsx @@ -63,4 +63,3 @@ export function createControllerComponent -} - -type IonRouterOutletProps = RouteComponentProps & { - id?: string; - children?: React.ReactElement[] | React.ReactElement; -}; - -type IonRouterOutletState = {} - -class IonRouterOutletUnWrapped extends React.Component { - containerEl: React.RefObject = React.createRef(); - context!: React.ContextType; - id: string; - - constructor(props: IonRouterOutletProps) { - super(props); - this.id = this.props.id || generateUniqueId(); - } - - componentDidMount() { - const views: ViewItem[] = []; - let activeId: string; - React.Children.forEach(this.props.children, (child: React.ReactElement) => { - if (child.type === Switch) { - /** - * If the first child is a Switch, loop through its children to build the viewStack - */ - React.Children.forEach(child.props.children, (grandChild: React.ReactElement) => { - addView.call(this, grandChild); - }); - } else { - addView.call(this, child); - } - }); - this.context.registerViewStack(this.id, activeId, views, this.containerEl.current, this.props.location); - - function addView(child: React.ReactElement) { - const location = this.props.history.location; - const viewId = generateUniqueId(); - const key = generateUniqueId(); - const element = child; - const match: ViewItem['match'] = matchPath(location.pathname, child.props); - const view: ViewItem = { - id: viewId, - key, - match, - element, - mount: true, - show: !!match, - ref: React.createRef(), - childProps: child.props - }; - if (!!match) { - activeId = viewId; - }; - views.push(view); - return activeId; - } - } - - componentWillUnmount() { - this.context.removeViewStack(this.id); - } - - renderChild(item: ViewItem) { - const component = React.cloneElement(item.element, { - computedMatch: item.match - }); - return component; - } - - render() { - return ( - - {context => { - this.context = context; - const viewStack = context.viewStacks[this.id]; - const activeId = viewStack ? viewStack.activeId : ''; - const views = (viewStack || { views: [] }).views.filter(x => x.show); - return ( - - {views.map((item) => { - let props: any = {}; - if (item.id === activeId) { - props = { - 'className': ' ion-page-invisible' - }; - } else { - props = { - 'aria-hidden': true, - 'className': 'ion-page-hidden' - }; - } - - return ( - - - {this.renderChild(item)} - - - ); - })} - - ); - }} - - ); - } -} - -export const IonRouterOutlet = /*@__PURE__*/withRouter(IonRouterOutletUnWrapped); diff --git a/react/src/components/navigation/IonTabs.tsx b/react/src/components/navigation/IonTabs.tsx index f5142e2908..9eb0c0017e 100644 --- a/react/src/components/navigation/IonTabs.tsx +++ b/react/src/components/navigation/IonTabs.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { IonTabBar } from './IonTabBar'; -import { IonRouterOutlet } from './IonRouterOutlet'; +import { IonRouterOutlet } from './routing/IonRouterOutlet'; type Props = { children: React.ReactNode; @@ -25,7 +25,6 @@ const tabsInner: React.CSSProperties = { contain: 'layout size style' }; - export class IonTabs extends React.Component { render() { diff --git a/react/src/components/navigation/ViewItemManager.tsx b/react/src/components/navigation/ViewItemManager.tsx index 0a3cb515f0..5e490a9517 100644 --- a/react/src/components/navigation/ViewItemManager.tsx +++ b/react/src/components/navigation/ViewItemManager.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { IonLifeCycleContext } from '../../lifecycle'; import { DefaultIonLifeCycleContext } from '../../lifecycle/IonLifeCycleContext'; -import { NavContext } from './NavContext'; +import { NavContext } from './routing/NavContext'; interface StackItemManagerProps { id: string; diff --git a/react/src/components/navigation/routing/IonRouterOutlet.tsx b/react/src/components/navigation/routing/IonRouterOutlet.tsx new file mode 100644 index 0000000000..274afb4720 --- /dev/null +++ b/react/src/components/navigation/routing/IonRouterOutlet.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import { generateUniqueId } from '../../utils/utils'; +import { IonRouterOutletInner } from '../../proxies'; +import { View } from '../../View'; +import { NavContext } from './NavContext'; +import { ViewItemManager } from '../ViewItemManager'; + +type IonRouterOutletProps = { + id?: string; +}; + +type IonRouterOutletState = {} + +export class IonRouterOutlet extends React.Component { + containerEl: React.RefObject = React.createRef(); + context!: React.ContextType; + id: string; + + constructor(props: IonRouterOutletProps) { + super(props); + this.id = this.props.id || generateUniqueId(); + } + + componentDidMount() { + this.context.setupIonRouter(this.id, this.props.children, this.containerEl.current); + } + + componentWillUnmount() { + this.context.removeViewStack(this.id); + } + + render() { + return ( + + {context => { + this.context = context; + const viewStack = context.viewStacks[this.id]; + const activeId = viewStack ? viewStack.activeId : ''; + const views = (viewStack || { views: [] }).views.filter(x => x.show); + return ( + + {views.map((item) => { + let props: any = {}; + if (item.id === activeId) { + props = { + 'className': ' ion-page-invisible' + }; + } + return ( + + + {this.context.renderChild(item)} + + + ); + })} + + ); + }} + + ); + } +} diff --git a/react/src/components/navigation/NavContext.ts b/react/src/components/navigation/routing/NavContext.ts similarity index 73% rename from react/src/components/navigation/NavContext.ts rename to react/src/components/navigation/routing/NavContext.ts index 525a1316d5..0f335ffdae 100644 --- a/react/src/components/navigation/NavContext.ts +++ b/react/src/components/navigation/routing/NavContext.ts @@ -1,13 +1,10 @@ -import React from 'react'; -import { ViewItem } from './ViewItem'; +import React, { ReactNode } from 'react'; +import { ViewItem } from './ReactRouter/ViewItem'; import { NavDirection } from '@ionic/core'; -import { Location } from 'history'; - export interface ViewStack { routerOutlet: HTMLIonRouterOutletElement; activeId?: string, - // prevId?: string, views: ViewItem[] } @@ -18,8 +15,9 @@ export interface ViewStacks { export interface NavContextState { hideView: (viewId: string) => void; viewStacks: ViewStacks; - registerViewStack: (stack: string, activeId: string, stackItems: ViewItem[], ionRouterOutlet: HTMLIonRouterOutletElement, location: Location) => void; + setupIonRouter: (id: string, children: ReactNode, routerOutlet: HTMLIonRouterOutletElement) => void; removeViewStack: (stack: string) => void; + renderChild: (item: ViewItem) => void; goBack: (defaultHref?: string) => void; transitionView: (enteringEl: HTMLElement, leavingEl: HTMLElement, ionRouterOuter: HTMLIonRouterOutletElement, direction: NavDirection) => void; } @@ -28,8 +26,9 @@ export const NavContext = /*@__PURE__*/React.createContext({ viewStacks: {}, hideView: () => { navContextNotFoundError(); }, goBack: () => { navContextNotFoundError(); }, - registerViewStack: () => { navContextNotFoundError(); }, + setupIonRouter: () => { navContextNotFoundError() }, removeViewStack: () => { navContextNotFoundError(); }, + renderChild: () => { navContextNotFoundError(); }, transitionView: () => { navContextNotFoundError(); } }); diff --git a/react/src/components/navigation/IonRouter.tsx b/react/src/components/navigation/routing/ReactRouter/IonReactRouter.tsx similarity index 67% rename from react/src/components/navigation/IonRouter.tsx rename to react/src/components/navigation/routing/ReactRouter/IonReactRouter.tsx index 6dda92b502..dfd4137f35 100644 --- a/react/src/components/navigation/IonRouter.tsx +++ b/react/src/components/navigation/routing/ReactRouter/IonReactRouter.tsx @@ -1,26 +1,32 @@ -import React from 'react'; -import { withRouter, RouteComponentProps, matchPath, match, Redirect } from 'react-router-dom'; +import React, { ReactNode } from 'react'; +import { RouteComponentProps, matchPath, match, Redirect, Switch, RouteProps, BrowserRouterProps, BrowserRouter, withRouter } from 'react-router-dom'; import { UnregisterCallback, Action as HistoryAction, Location as HistoryLocation } from 'history'; -import { NavContext, NavContextState, ViewStacks, ViewStack } from './NavContext'; +import { NavContext, NavContextState, ViewStacks, ViewStack } from '../NavContext'; import { ViewItem } from './ViewItem'; import { NavDirection } from '@ionic/core'; -import { generateUniqueId } from '../utils'; +import { generateUniqueId } from '../../../utils'; -interface IonRouterProps extends RouteComponentProps { } -interface IonRouterState extends NavContextState { } +interface IonReactRouterProps extends RouteComponentProps { } +interface IonReactRouterState extends NavContextState { } -class IonRouter extends React.Component { +interface IonRouteData { + match: match<{ tab: string }>; + childProps: RouteProps; +} + +class IonNavManager extends React.Component { listenUnregisterCallback: UnregisterCallback; activeViewId?: string; prevViewId?: string; - constructor(props: IonRouterProps) { + constructor(props: IonReactRouterProps) { super(props); this.state = { viewStacks: {}, hideView: this.hideView.bind(this), - registerViewStack: this.registerView.bind(this), + setupIonRouter: this.setupIonRouter.bind(this), removeViewStack: this.removeViewStack.bind(this), + renderChild: this.renderChild.bind(this), goBack: this.goBack.bind(this), transitionView: this.transitionView.bind(this) }; @@ -47,14 +53,14 @@ class IonRouter extends React.Component { } findViewInfoByLocation(location: HistoryLocation, viewStacks: ViewStacks) { - let view: ViewItem; - let match: match<{ tab: string }>; + let view: ViewItem; + let match: IonRouteData["match"]; let viewStack: ViewStack; const keys = Object.keys(viewStacks); keys.some(key => { const vs = viewStacks[key]; return vs.views.some(x => { - match = matchPath(location.pathname, x.childProps) + match = matchPath(location.pathname, x.routeData.childProps) if (match) { view = x; viewStack = vs; @@ -64,12 +70,12 @@ class IonRouter extends React.Component { }); }) - const result: { view: ViewItem, viewStack: ViewStack, match: ViewItem['match'] } = { view, viewStack, match }; + const result = { view, viewStack, match }; return result; } findViewInfoById(id: string, viewStacks: ViewStacks) { - let view: ViewItem; + let view: ViewItem; let viewStack: ViewStack; const keys = Object.keys(viewStacks); keys.some(key => { @@ -96,7 +102,7 @@ class IonRouter extends React.Component { const { view: leavingView } = this.findViewInfoById(this.activeViewId, viewStacks); - if (leavingView && leavingView.match.url === location.pathname) { + if (leavingView && leavingView.routeData.match.url === location.pathname) { return; } @@ -111,13 +117,13 @@ class IonRouter extends React.Component { enteringView.show = true; enteringView.mount = true; - enteringView.match = match; + enteringView.routeData.match = match; enteringViewStack.activeId = enteringView.id; this.activeViewId = enteringView.id; if (leavingView) { this.prevViewId = leavingView.id - if (leavingView.match.params.tab === enteringView.match.params.tab) { + if (leavingView.routeData.match.params.tab === enteringView.routeData.match.params.tab) { if (action === 'PUSH') { direction = direction || 'forward'; } else { @@ -131,7 +137,7 @@ class IonRouter extends React.Component { * We assume Routes with render props are redirects, because of this users should not use * the render prop for non redirects, and instead provide a component in its place. */ - if(leavingView.element.type === Redirect || leavingView.element.props.render) { + if (leavingView.element.type === Redirect || leavingView.element.props.render) { leavingView.mount = false; leavingView.show = false; } @@ -155,7 +161,50 @@ class IonRouter extends React.Component { this.listenUnregisterCallback(); } - registerView(stack: string, activeId: string, stackItems: ViewItem[], routerOutlet: HTMLIonRouterOutletElement, location: HistoryLocation) { + setupIonRouter(id: string, children: ReactNode, routerOutlet: HTMLIonRouterOutletElement) { + const views: ViewItem[] = []; + let activeId: string; + React.Children.forEach(children, (child: React.ReactElement) => { + if (child.type === Switch) { + /** + * If the first child is a Switch, loop through its children to build the viewStack + */ + React.Children.forEach(child.props.children, (grandChild: React.ReactElement) => { + addView.call(this, grandChild); + }); + } else { + addView.call(this, child); + } + }); + this.registerViewStack(id, activeId, views, routerOutlet, this.props.location); + + function addView(child: React.ReactElement) { + const location = this.props.history.location; + const viewId = generateUniqueId(); + const key = generateUniqueId(); + const element = child; + const match: IonRouteData['match'] = matchPath(location.pathname, child.props); + const view: ViewItem = { + id: viewId, + key, + routeData: { + match, + childProps: child.props + }, + element, + mount: true, + show: !!match, + ref: React.createRef() + }; + if (!!match) { + activeId = viewId; + }; + views.push(view); + return activeId; + } + } + + registerViewStack(stack: string, activeId: string, stackItems: ViewItem[], routerOutlet: HTMLIonRouterOutletElement, location: HistoryLocation) { this.setState((prevState) => { const prevViewStacks = Object.assign({}, prevState.viewStacks); prevViewStacks[stack] = { @@ -191,10 +240,17 @@ class IonRouter extends React.Component { }); } + renderChild(item: ViewItem) { + const component = React.cloneElement(item.element, { + computedMatch: item.routeData.match + }); + return component; + } + findActiveView(views: ViewItem[]) { - let view: ViewItem | undefined; + let view: ViewItem | undefined; views.some(x => { - const match = matchPath(this.props.location.pathname, x.childProps) + const match = matchPath(this.props.location.pathname, x.routeData.childProps) if (match) { view = x; return true; @@ -209,7 +265,7 @@ class IonRouter extends React.Component { if (leavingView) { const { view: enteringView } = this.findViewInfoById(leavingView.prevId, this.state.viewStacks); if (enteringView) { - this.props.history.replace(enteringView.match.url, { direction: 'back' }); + this.props.history.replace(enteringView.routeData.match.url, { direction: 'back' }); } else { this.props.history.replace(defaultHref, { direction: 'back' }); } @@ -260,4 +316,16 @@ class IonRouter extends React.Component { } }; -export const IonRouterWrapped = withRouter(IonRouter); +const IonNavManagerWithRouter = withRouter(IonNavManager); +IonNavManagerWithRouter.displayName = 'IonNavManager'; + +export class IonReactRouter extends React.Component { + render() { + const { children, ...props } = this.props; + return ( + + {children} + + ); + } +} diff --git a/react/src/components/navigation/ViewItem.ts b/react/src/components/navigation/routing/ReactRouter/ViewItem.ts similarity index 53% rename from react/src/components/navigation/ViewItem.ts rename to react/src/components/navigation/routing/ReactRouter/ViewItem.ts index 377c6521c7..499da0b1be 100644 --- a/react/src/components/navigation/ViewItem.ts +++ b/react/src/components/navigation/routing/ReactRouter/ViewItem.ts @@ -1,13 +1,10 @@ -import { match, RouteProps } from 'react-router-dom'; - -export type ViewItem = { +export type ViewItem = { id: string; key: string; - match: match<{ tab: string }>; element: React.ReactElement; ref?: React.RefObject; + routeData: RouteData; prevId?: string; mount: boolean; show: boolean; - childProps?: RouteProps; } diff --git a/react/src/components/proxies.ts b/react/src/components/proxies.ts index a19d93524e..eccb05e50d 100644 --- a/react/src/components/proxies.ts +++ b/react/src/components/proxies.ts @@ -5,7 +5,7 @@ import { JSX as IoniconsJSX } from 'ionicons'; // ionicons -export const IonIcon = /*@__PURE__*/createReactComponent('ion-icon'); +export const IonIcon = /*@__PURE__*/createReactComponent('ion-icon', ['icon']); // /*@__PURE__*/createReactComponent export const IonTabBarInner = /*@__PURE__*/createReactComponent('ion-tab-bar'); @@ -14,7 +14,7 @@ export const IonBackButtonInner = /*@__PURE__*/createReactComponent('ion-tab'); export const IonTabButton = /*@__PURE__*/createReactComponent('ion-tab-button'); export const IonAnchor = /*@__PURE__*/createReactComponent('ion-anchor'); -export const IonApp = /*@__PURE__*/createReactComponent('ion-app'); +export const IonAppInner = /*@__PURE__*/createReactComponent('ion-app'); export const IonAvatar = /*@__PURE__*/createReactComponent('ion-avatar'); export const IonBackdrop = /*@__PURE__*/createReactComponent('ion-backdrop'); export const IonBadge = /*@__PURE__*/createReactComponent('ion-badge'); diff --git a/react/src/components/utils/IonicContext.ts b/react/src/components/utils/IonicContext.ts new file mode 100644 index 0000000000..57bca20cb8 --- /dev/null +++ b/react/src/components/utils/IonicContext.ts @@ -0,0 +1,15 @@ +import React from 'react'; +import { Platform } from './platform'; +import { IonicConfig } from '@ionic/core'; + +export interface IonicContextState { + getConfig: () => IonicConfig | void; + setConfig: (config: IonicConfig) => void; + platform: Platform | undefined; +} + +export const IonicContext = /*@__PURE__*/React.createContext({ + getConfig: () => {}, + setConfig: () => {}, + platform: undefined +}); diff --git a/react/src/components/utils/config.ts b/react/src/components/utils/config.ts new file mode 100644 index 0000000000..5c91bbe417 --- /dev/null +++ b/react/src/components/utils/config.ts @@ -0,0 +1,25 @@ +import { Config, IonicConfig } from '@ionic/core'; + +export function getConfig() { + const coreConfig = getCoreConfig(); + const config: IonicConfig = Array.from((coreConfig as any).m).reduce((obj: any, [key, value]) => { + obj[key] = value; + return obj; + }, {}); + return config; +} + +export function setConfig(config: IonicConfig) { + const coreConfig = getCoreConfig(); + coreConfig.reset(config); +} + +function getCoreConfig(): Config { + if (typeof (window as any) !== 'undefined') { + const Ionic = window.Ionic; + if (Ionic && Ionic.config) { + return window.Ionic.config; + } + } + return null; +} diff --git a/react/src/components/utils/index.ts b/react/src/components/utils/index.ts index 2d873d6ba2..2274d22bc8 100644 --- a/react/src/components/utils/index.ts +++ b/react/src/components/utils/index.ts @@ -8,3 +8,5 @@ export function generateUniqueId() { } export * from './attachEventProps'; +export * from './IonicContext'; +export * from './utilHooks'; diff --git a/react/src/components/utils/platform.ts b/react/src/components/utils/platform.ts new file mode 100644 index 0000000000..74fc2a3763 --- /dev/null +++ b/react/src/components/utils/platform.ts @@ -0,0 +1,140 @@ +import { Platforms, getPlatforms, isPlatform } from '@ionic/core'; + +export class Platform { + + constructor() { } + + /** + * @returns 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. | + * | ios | on a device running iOS. | + * | ipad | on an iPad device. | + * | iphone | on an iPhone device. | + * | phablet | on a phablet device. | + * | tablet | on a tablet device. | + * | electron | in Electron on a desktop device. | + * | pwa | as a PWA app. | + * | mobile | on a mobile device. | + * | mobileweb | on a mobile device in a browser. | + * | desktop | on a desktop device. | + * | hybrid | is a cordova or capacitor app. | + * + */ + is(platformName: Platforms) { + return isPlatform(window, platformName); + } + + /** + * @returns 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() { + return getPlatforms(window); + } + + /** + * Returns if this app is using right-to-left language direction or not. + * 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) + */ + get isRTL(): boolean { + return document.dir === 'rtl'; + } + + /** + * Get the query string parameter + */ + getQueryParam(key: string): string | null { + return readQueryParam(window.location.href, key); + } + + /** + * Returns `true` if the app is in landscape mode. + */ + isLandscape(): boolean { + return !this.isPortrait(); + } + + /** + * Returns `true` if the app is in portait mode. + */ + isPortrait(): boolean { + return window.matchMedia && window.matchMedia('(orientation: portrait)').matches; + } + + testUserAgent(expression: string): boolean { + const nav = window.navigator; + return !!(nav && nav.userAgent && nav.userAgent.indexOf(expression) >= 0); + } + + /** + * Get the current url. + */ + url() { + return window.location.href; + } + + + /** + * Gets the width of the platform's viewport using `window.innerWidth`. + */ + width() { + return window.innerWidth; + } + + /** + * Gets the height of the platform's viewport using `window.innerHeight`. + */ + height(): number { + return window.innerHeight; + } +} + +const 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/react/src/components/utils/utilHooks.ts b/react/src/components/utils/utilHooks.ts new file mode 100644 index 0000000000..fef125bbe3 --- /dev/null +++ b/react/src/components/utils/utilHooks.ts @@ -0,0 +1,10 @@ +import React from 'react'; +import { IonicContext } from './IonicContext'; +import { IonicConfig } from '@ionic/core'; + +export const useIonicConfig = () => { + const value = React.useContext(IonicContext); + const config = value.getConfig() || {}; + const hook: [IonicConfig, (config: IonicConfig) => void] = [config, value.setConfig]; + return hook; +} diff --git a/react/src/components/utils/utils.ts b/react/src/components/utils/utils.ts new file mode 100644 index 0000000000..d7fb878a35 --- /dev/null +++ b/react/src/components/utils/utils.ts @@ -0,0 +1,8 @@ +export const dashToPascalCase = (str: string) => str.toLowerCase().split('-').map(segment => segment.charAt(0).toUpperCase() + segment.slice(1)).join(''); + +export function generateUniqueId() { + return ([1e7].toString() + -1e3.toString() + -4e3.toString() + -8e3.toString() + -1e11.toString()).replace(/[018]/g, function(c: any) { + const random = crypto.getRandomValues(new Uint8Array(1)) as Uint8Array; + return (c ^ random[0] & 15 >> c / 4).toString(16); + }); +} diff --git a/react/src/globals.ts b/react/src/globals.ts new file mode 100644 index 0000000000..7253464170 --- /dev/null +++ b/react/src/globals.ts @@ -0,0 +1,4 @@ +interface Window { + cordova: any; + Ionic: any; +} diff --git a/react/src/index.ts b/react/src/index.ts index d9edbcf65b..b19667f31f 100644 --- a/react/src/index.ts +++ b/react/src/index.ts @@ -1,5 +1,3 @@ export * from './components'; export * from './types'; export * from './lifecycle'; - -export { setupConfig } from '@ionic/core';