mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-16 10:01:59 +08:00
chore() react beta 6 release (#18588)
* fix(react) attribute data for web components fix * fix(react) attribute data for web components fix * wip * putting back cause issues * update version 0.6-6 * wip * fix(react) - fixing flash between moving between tabs * update version to 0.6-7 * update to core 4.6 * update to 6-9 and tab button selected fix * wrapping react router * beta 6 release
This commit is contained in:
@ -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",
|
||||
|
37
react/src/components/IonApp.tsx
Normal file
37
react/src/components/IonApp.tsx
Normal file
@ -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<IonAppProps, IonAppState> {
|
||||
|
||||
constructor(props: IonAppProps) {
|
||||
super(props);
|
||||
|
||||
const ionicPlatform = new Platform();
|
||||
this.state = {
|
||||
getConfig: getConfig,
|
||||
setConfig: setConfig,
|
||||
platform: ionicPlatform
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<IonicContext.Provider value={this.state}>
|
||||
<IonAppInner>{this.props.children}</IonAppInner>
|
||||
</IonicContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
export default IonAppProps;
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||
import ReactDom from 'react-dom';
|
||||
import { dashToPascalCase, attachEventProps } from './utils';
|
||||
|
||||
export function createReactComponent<PropType, ElementType>(tagName: string) {
|
||||
export function createReactComponent<PropType, ElementType>(tagName: string, attributeValues: string[] = []) {
|
||||
const displayName = dashToPascalCase(tagName);
|
||||
|
||||
type IonicReactInternalProps = {
|
||||
@ -17,11 +17,9 @@ export function createReactComponent<PropType, ElementType>(tagName: string) {
|
||||
}
|
||||
|
||||
class ReactComponent extends React.Component<InternalProps> {
|
||||
componentRef: React.RefObject<ElementType>;
|
||||
|
||||
constructor(props: PropType & IonicReactInternalProps) {
|
||||
super(props);
|
||||
this.componentRef = React.createRef();
|
||||
}
|
||||
|
||||
static get displayName() {
|
||||
@ -40,10 +38,17 @@ export function createReactComponent<PropType, ElementType>(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
|
||||
|
@ -63,4 +63,3 @@ export function createControllerComponent<OptionsType extends object, LoadingEle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
|
||||
import { defineCustomElements } from '@ionic/core/loader';
|
||||
export { AlertButton, AlertInput } from '@ionic/core';
|
||||
export { IonApp } from './IonApp'
|
||||
export * from './proxies';
|
||||
|
||||
// createControllerComponent
|
||||
@ -17,8 +18,38 @@ export { IonPopover } from './IonPopover';
|
||||
export { IonPage } from './IonPage';
|
||||
export { IonTabs } from './navigation/IonTabs';
|
||||
export { IonTabBar } from './navigation/IonTabBar';
|
||||
export { IonRouterOutlet } from './navigation/IonRouterOutlet';
|
||||
export { IonBackButton } from './navigation/IonBackButton';
|
||||
export { IonRouterWrapped as IonRouter } from './navigation/IonRouter';
|
||||
|
||||
// Routing
|
||||
export { IonRouterOutlet } from './navigation/routing/IonRouterOutlet';
|
||||
export { IonReactRouter } from './navigation/routing/ReactRouter/IonReactRouter';
|
||||
|
||||
// Ionic Context
|
||||
export { IonicContext } from './utils/IonicContext';
|
||||
export { useIonicConfig } from './utils/utilHooks';
|
||||
|
||||
// Icons
|
||||
import { addIcons } from 'ionicons';
|
||||
import { close, reorder, menu, arrowDown, arrowForward, arrowBack, search, closeCircle } from 'ionicons/icons';
|
||||
|
||||
defineCustomElements(window);
|
||||
|
||||
// Icons that are used by internal components
|
||||
addIcons({
|
||||
'ios-close': close.ios,
|
||||
'md-close': close.md,
|
||||
'ios-reorder': reorder.ios,
|
||||
'md-reorder': reorder.md,
|
||||
'ios-menu': menu.ios,
|
||||
'md-menu': menu.md,
|
||||
'ios-arrow-forward': arrowForward.ios,
|
||||
'md-arrow-forward': arrowForward.md,
|
||||
'ios-arrow-back': arrowBack.ios,
|
||||
'md-arrow-back': arrowBack.md,
|
||||
'ios-arrow-down': arrowDown.ios,
|
||||
'md-arrow-down': arrowDown.md,
|
||||
'ios-search': search.ios,
|
||||
'md-search': search.md,
|
||||
'ios-close-circle': closeCircle.ios,
|
||||
'md-close-circle': closeCircle.md,
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { JSX as LocalJSX } from '@ionic/core';
|
||||
import React from 'react';
|
||||
import { NavContext } from './NavContext';
|
||||
import { NavContext } from './routing/NavContext';
|
||||
import { IonBackButtonInner } from '../proxies';
|
||||
|
||||
type BackButtonProps = LocalJSX.IonBackButton & {
|
||||
|
@ -1,129 +0,0 @@
|
||||
import React from 'react';
|
||||
import { withRouter, RouteComponentProps, matchPath, RouteProps, match, Switch } from 'react-router';
|
||||
import { generateUniqueId } from '../utils';
|
||||
import { IonRouterOutletInner } from '../proxies';
|
||||
import { View } from '../View';
|
||||
import { NavContext } from './NavContext';
|
||||
import { ViewItemManager } from './ViewItemManager';
|
||||
import { ViewItem } from './ViewItem';
|
||||
|
||||
type ChildProps = RouteProps & {
|
||||
computedMatch: match<any>
|
||||
}
|
||||
|
||||
type IonRouterOutletProps = RouteComponentProps & {
|
||||
id?: string;
|
||||
children?: React.ReactElement<ChildProps>[] | React.ReactElement<ChildProps>;
|
||||
};
|
||||
|
||||
type IonRouterOutletState = {}
|
||||
|
||||
class IonRouterOutletUnWrapped extends React.Component<IonRouterOutletProps, IonRouterOutletState> {
|
||||
containerEl: React.RefObject<HTMLIonRouterOutletElement> = React.createRef();
|
||||
context!: React.ContextType<typeof NavContext>;
|
||||
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<ChildProps>) => {
|
||||
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<ChildProps>) => {
|
||||
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<any>) {
|
||||
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 (
|
||||
<NavContext.Consumer>
|
||||
{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 (
|
||||
<IonRouterOutletInner data-id={this.id} ref={this.containerEl}>
|
||||
{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 (
|
||||
<ViewItemManager
|
||||
id={item.id}
|
||||
key={item.key}
|
||||
mount={item.mount}
|
||||
>
|
||||
<View
|
||||
ref={item.ref}
|
||||
{...props}
|
||||
>
|
||||
{this.renderChild(item)}
|
||||
</View>
|
||||
</ViewItemManager>
|
||||
);
|
||||
})}
|
||||
</IonRouterOutletInner>
|
||||
);
|
||||
}}
|
||||
</NavContext.Consumer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const IonRouterOutlet = /*@__PURE__*/withRouter(IonRouterOutletUnWrapped);
|
@ -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<Props> {
|
||||
|
||||
render() {
|
||||
|
@ -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;
|
||||
|
70
react/src/components/navigation/routing/IonRouterOutlet.tsx
Normal file
70
react/src/components/navigation/routing/IonRouterOutlet.tsx
Normal file
@ -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<IonRouterOutletProps, IonRouterOutletState> {
|
||||
containerEl: React.RefObject<HTMLIonRouterOutletElement> = React.createRef();
|
||||
context!: React.ContextType<typeof NavContext>;
|
||||
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 (
|
||||
<NavContext.Consumer>
|
||||
{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 (
|
||||
<IonRouterOutletInner data-id={this.id} ref={this.containerEl}>
|
||||
{views.map((item) => {
|
||||
let props: any = {};
|
||||
if (item.id === activeId) {
|
||||
props = {
|
||||
'className': ' ion-page-invisible'
|
||||
};
|
||||
}
|
||||
return (
|
||||
<ViewItemManager
|
||||
id={item.id}
|
||||
key={item.key}
|
||||
mount={item.mount}
|
||||
>
|
||||
<View
|
||||
ref={item.ref}
|
||||
{...props}
|
||||
>
|
||||
{this.context.renderChild(item)}
|
||||
</View>
|
||||
</ViewItemManager>
|
||||
);
|
||||
})}
|
||||
</IonRouterOutletInner>
|
||||
);
|
||||
}}
|
||||
</NavContext.Consumer>
|
||||
);
|
||||
}
|
||||
}
|
@ -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<NavContextState>({
|
||||
viewStacks: {},
|
||||
hideView: () => { navContextNotFoundError(); },
|
||||
goBack: () => { navContextNotFoundError(); },
|
||||
registerViewStack: () => { navContextNotFoundError(); },
|
||||
setupIonRouter: () => { navContextNotFoundError() },
|
||||
removeViewStack: () => { navContextNotFoundError(); },
|
||||
renderChild: () => { navContextNotFoundError(); },
|
||||
transitionView: () => { navContextNotFoundError(); }
|
||||
});
|
||||
|
@ -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<IonRouterProps, IonRouterState> {
|
||||
interface IonRouteData {
|
||||
match: match<{ tab: string }>;
|
||||
childProps: RouteProps;
|
||||
}
|
||||
|
||||
class IonNavManager extends React.Component<IonReactRouterProps, IonReactRouterState> {
|
||||
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<IonRouterProps, IonRouterState> {
|
||||
}
|
||||
|
||||
findViewInfoByLocation(location: HistoryLocation, viewStacks: ViewStacks) {
|
||||
let view: ViewItem;
|
||||
let match: match<{ tab: string }>;
|
||||
let view: ViewItem<IonRouteData>;
|
||||
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<IonRouterProps, IonRouterState> {
|
||||
});
|
||||
})
|
||||
|
||||
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<IonRouteData>;
|
||||
let viewStack: ViewStack;
|
||||
const keys = Object.keys(viewStacks);
|
||||
keys.some(key => {
|
||||
@ -96,7 +102,7 @@ class IonRouter extends React.Component<IonRouterProps, IonRouterState> {
|
||||
|
||||
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<IonRouterProps, IonRouterState> {
|
||||
|
||||
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<IonRouterProps, IonRouterState> {
|
||||
* 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<IonRouterProps, IonRouterState> {
|
||||
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<any>) {
|
||||
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<IonRouteData> = {
|
||||
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<IonRouterProps, IonRouterState> {
|
||||
});
|
||||
}
|
||||
|
||||
renderChild(item: ViewItem<IonRouteData>) {
|
||||
const component = React.cloneElement(item.element, {
|
||||
computedMatch: item.routeData.match
|
||||
});
|
||||
return component;
|
||||
}
|
||||
|
||||
findActiveView(views: ViewItem[]) {
|
||||
let view: ViewItem | undefined;
|
||||
let view: ViewItem<IonRouteData> | 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<IonRouterProps, IonRouterState> {
|
||||
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<IonRouterProps, IonRouterState> {
|
||||
}
|
||||
};
|
||||
|
||||
export const IonRouterWrapped = withRouter(IonRouter);
|
||||
const IonNavManagerWithRouter = withRouter(IonNavManager);
|
||||
IonNavManagerWithRouter.displayName = 'IonNavManager';
|
||||
|
||||
export class IonReactRouter extends React.Component<BrowserRouterProps> {
|
||||
render() {
|
||||
const { children, ...props } = this.props;
|
||||
return (
|
||||
<BrowserRouter {...props}>
|
||||
<IonNavManagerWithRouter>{children}</IonNavManagerWithRouter>
|
||||
</BrowserRouter>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,13 +1,10 @@
|
||||
import { match, RouteProps } from 'react-router-dom';
|
||||
|
||||
export type ViewItem = {
|
||||
export type ViewItem<RouteData = any> = {
|
||||
id: string;
|
||||
key: string;
|
||||
match: match<{ tab: string }>;
|
||||
element: React.ReactElement<any>;
|
||||
ref?: React.RefObject<HTMLElement>;
|
||||
routeData: RouteData;
|
||||
prevId?: string;
|
||||
mount: boolean;
|
||||
show: boolean;
|
||||
childProps?: RouteProps;
|
||||
}
|
@ -5,7 +5,7 @@ import { JSX as IoniconsJSX } from 'ionicons';
|
||||
|
||||
|
||||
// ionicons
|
||||
export const IonIcon = /*@__PURE__*/createReactComponent<IoniconsJSX.IonIcon & ReactProps, HTMLIonIconElement>('ion-icon');
|
||||
export const IonIcon = /*@__PURE__*/createReactComponent<IoniconsJSX.IonIcon & ReactProps, HTMLIonIconElement>('ion-icon', ['icon']);
|
||||
|
||||
// /*@__PURE__*/createReactComponent
|
||||
export const IonTabBarInner = /*@__PURE__*/createReactComponent<JSX.IonTabBar & ReactProps, HTMLIonTabBarElement>('ion-tab-bar');
|
||||
@ -14,7 +14,7 @@ export const IonBackButtonInner = /*@__PURE__*/createReactComponent<JSX.IonBackB
|
||||
export const IonTab = /*@__PURE__*/createReactComponent<JSX.IonTab & ReactProps, HTMLIonTabElement>('ion-tab');
|
||||
export const IonTabButton = /*@__PURE__*/createReactComponent<JSX.IonTabButton & ReactProps, HTMLIonTabButtonElement>('ion-tab-button');
|
||||
export const IonAnchor = /*@__PURE__*/createReactComponent<JSX.IonAnchor & ReactProps, HTMLIonAnchorElement>('ion-anchor');
|
||||
export const IonApp = /*@__PURE__*/createReactComponent<JSX.IonApp & ReactProps, HTMLIonAppElement>('ion-app');
|
||||
export const IonAppInner = /*@__PURE__*/createReactComponent<JSX.IonApp & ReactProps, HTMLIonAppElement>('ion-app');
|
||||
export const IonAvatar = /*@__PURE__*/createReactComponent<JSX.IonAvatar & ReactProps, HTMLIonAvatarElement>('ion-avatar');
|
||||
export const IonBackdrop = /*@__PURE__*/createReactComponent<JSX.IonBackdrop & ReactProps, HTMLIonBackdropElement>('ion-backdrop');
|
||||
export const IonBadge = /*@__PURE__*/createReactComponent<JSX.IonBadge & ReactProps, HTMLIonBadgeElement>('ion-badge');
|
||||
|
15
react/src/components/utils/IonicContext.ts
Normal file
15
react/src/components/utils/IonicContext.ts
Normal file
@ -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<IonicContextState>({
|
||||
getConfig: () => {},
|
||||
setConfig: () => {},
|
||||
platform: undefined
|
||||
});
|
25
react/src/components/utils/config.ts
Normal file
25
react/src/components/utils/config.ts
Normal file
@ -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;
|
||||
}
|
@ -8,3 +8,5 @@ export function generateUniqueId() {
|
||||
}
|
||||
|
||||
export * from './attachEventProps';
|
||||
export * from './IonicContext';
|
||||
export * from './utilHooks';
|
||||
|
140
react/src/components/utils/platform.ts
Normal file
140
react/src/components/utils/platform.ts
Normal file
@ -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 `<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)
|
||||
*/
|
||||
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;
|
||||
};
|
10
react/src/components/utils/utilHooks.ts
Normal file
10
react/src/components/utils/utilHooks.ts
Normal file
@ -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;
|
||||
}
|
8
react/src/components/utils/utils.ts
Normal file
8
react/src/components/utils/utils.ts
Normal file
@ -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);
|
||||
});
|
||||
}
|
4
react/src/globals.ts
Normal file
4
react/src/globals.ts
Normal file
@ -0,0 +1,4 @@
|
||||
interface Window {
|
||||
cordova: any;
|
||||
Ionic: any;
|
||||
}
|
@ -1,5 +1,3 @@
|
||||
export * from './components';
|
||||
export * from './types';
|
||||
export * from './lifecycle';
|
||||
|
||||
export { setupConfig } from '@ionic/core';
|
||||
|
Reference in New Issue
Block a user