feature(react): rc2 release

* fix(): add a page with class ion-page back to ionrouteroutlet - fixes #19146

* wip

* fix(react): attributes show up in dom

* chore(): adding ion-page to core wip

* wip

* fix destroy method

* wrap dom writes in raf

* Add comments

* fix(react): IonPage work

* chore(): ionpage rc3 changelog text

* fix(): syncing ion-page in a new way to get rid of timeout loop

* chore(): ViewStacks refactor out of router

* fix(): remove unused method in router

* wip - before setActiveView rework

* fix(): react router ion page work

* chore(): cleanup and dev release

* fix(): remove need to name tabs

* chore(): adding dev mode helpers

* fix(): adding className prop to back button fixes #19251

* fix(): routerDirection changes

* chore(): rc2 release

* fix(): fix react version in package

* chores(): build kickoff
This commit is contained in:
Ely Lucas
2019-09-12 14:25:37 -06:00
committed by GitHub
parent aec2936725
commit 73dd70d756
32 changed files with 822 additions and 611 deletions

View File

@ -1,25 +1,36 @@
import React from 'react';
import { createForwardRef } from './utils';
import { NavContext } from '../contexts/NavContext';
type Props = React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
import { ReactProps } from './ReactProps';
type InternalProps = Props & {
forwardedRef?: React.Ref<HTMLDivElement>
};
export const IonPage = /*@__PURE__*/(() => class IonPageInternal extends React.Component<React.HTMLAttributes<HTMLElement> & ReactProps> {
context!: React.ContextType<typeof NavContext>;
ref = React.createRef<HTMLDivElement>();
type ExternalProps = Props & {
ref?: React.Ref<HTMLDivElement>
};
componentDidMount() {
if (this.context && this.ref.current) {
if (this.context.hasIonicRouter()) {
this.context.registerIonPage(this.ref.current);
}
}
}
const IonPageInternal: React.FC<InternalProps> = ({ children, forwardedRef, className, ...props }) => (
<div
className={className !== undefined ? `ion-page ${className}` : 'ion-page'}
ref={forwardedRef}
{...props}
>
{children}
</div>
);
render() {
const { className, children, ...props } = this.props;
export const IonPage = /*@__PURE__*/createForwardRef<ExternalProps, HTMLDivElement>(IonPageInternal, 'IonPage');
return (
<div className={className ? `ion-page ${className}` : 'ion-page'} ref={this.ref} {...props}>
{children}
</div>
);
}
static get displayName() {
return 'IonPage';
}
static get contextType() {
return NavContext;
}
})();

View File

@ -0,0 +1,46 @@
import { JSX as LocalJSX } from '@ionic/core';
import React from 'react';
import { NavContext } from '../contexts/NavContext';
import { ReactProps } from './ReactProps';
import { IonRouterOutletInner } from './inner-proxies';
import { createForwardRef } from './utils';
type Props = LocalJSX.IonRouterOutlet & {
ref?: React.RefObject<any>;
};
type InternalProps = Props & {
forwardedRef: any;
};
const IonRouterOutletContainer = /*@__PURE__*/(() => class extends React.Component<InternalProps> {
context!: React.ContextType<typeof NavContext>;
render() {
const StackManager = this.context.getStackManager();
return (
this.context.hasIonicRouter() ? (
<StackManager>
<IonRouterOutletInner ref={this.props.forwardedRef} {...this.props}>
{this.props.children}
</IonRouterOutletInner>
</StackManager>
) : (
<IonRouterOutletInner ref={this.props.forwardedRef} {...this.props}>
{this.props.children}
</IonRouterOutletInner>
)
);
}
static get contextType() {
return NavContext;
}
})();
export const IonRouterOutlet = createForwardRef<Props & ReactProps, HTMLIonRouterOutletElement>(IonRouterOutletContainer, 'IonRouterOutlet');

View File

@ -1,10 +1,10 @@
import { RouterDirection } from '@ionic/core';
import React from 'react';
import ReactDom from 'react-dom';
import { NavContext } from '../contexts/NavContext';
import { ReactProps } from './ReactProps';
import { RouterDirection } from './hrefprops';
import { attachEventProps, createForwardRef, dashToPascalCase, isCoveredByReact } from './utils';
interface IonicReactInternalProps<ElementType> {

View File

@ -0,0 +1,5 @@
export declare type RouterDirection = 'forward' | 'back' | 'none';
export type HrefProps<T> = Omit<T, 'routerDirection'> & {
routerDirection?: RouterDirection;
};

View File

@ -20,9 +20,11 @@ export { IonPage } from './IonPage';
export { IonTabs } from './navigation/IonTabs';
export { IonTabBar } from './navigation/IonTabBar';
export { IonBackButton } from './navigation/IonBackButton';
export { IonRouterOutlet } from './IonRouterOutlet';
// Utils
export { isPlatform, getPlatforms } from './utils';
export { RouterDirection } from './hrefprops';
// Icons that are used by internal components
addIcons({

View File

@ -4,3 +4,4 @@ import { /*@__PURE__*/ createReactComponent } from './createComponent';
export const IonTabBarInner = /*@__PURE__*/createReactComponent<JSX.IonTabBar, HTMLIonTabBarElement>('ion-tab-bar');
export const IonBackButtonInner = /*@__PURE__*/createReactComponent<JSX.IonBackButton, HTMLIonBackButtonElement>('ion-back-button');
export const IonRouterOutletInner = /*@__PURE__*/createReactComponent<JSX.IonRouterOutlet, HTMLIonRouterOutletElement>('ion-router-outlet');

View File

@ -2,9 +2,10 @@ import { JSX as LocalJSX } from '@ionic/core';
import React from 'react';
import { NavContext } from '../../contexts/NavContext';
import { ReactProps } from '../ReactProps';
import { IonBackButtonInner } from '../inner-proxies';
type Props = LocalJSX.IonBackButton & {
type Props = LocalJSX.IonBackButton & ReactProps & {
ref?: React.RefObject<HTMLIonBackButtonElement>;
};
@ -13,13 +14,11 @@ export const IonBackButton = /*@__PURE__*/(() => class extends React.Component<P
clickButton = (e: MouseEvent) => {
const defaultHref = this.props.defaultHref;
if (defaultHref !== undefined) {
if (this.context.hasIonicRouter()) {
e.stopPropagation();
this.context.goBack(defaultHref);
} else {
window.location.href = defaultHref;
}
if (this.context.hasIonicRouter()) {
e.stopPropagation();
this.context.goBack(defaultHref);
} else if (defaultHref !== undefined) {
window.location.href = defaultHref;
}
}

View File

@ -7,7 +7,7 @@ import { IonTabButton } from '../proxies';
type Props = LocalJSX.IonTabBar & {
ref?: React.RefObject<HTMLIonTabBarElement>;
navigate: (path: string) => void;
navigate: (path: string, direction: 'back' | 'none') => void;
currentPath: string;
children?: React.ReactNode;
};
@ -68,10 +68,11 @@ const IonTabBarUnwrapped = /*@__PURE__*/(() => class extends React.Component<Pro
}
private onTabButtonClick = (e: CustomEvent<{ href: string, selected: boolean, tab: string }>) => {
const targetUrl = (this.state.activeTab === e.detail.tab) ?
this.state.tabs[e.detail.tab].originalHref :
this.state.tabs[e.detail.tab].currentHref;
this.props.navigate(targetUrl);
if (this.state.activeTab === e.detail.tab) {
this.props.navigate(this.state.tabs[e.detail.tab].originalHref, 'back');
} else {
this.props.navigate(this.state.tabs[e.detail.tab].currentHref, 'none');
}
}
private renderChild = (activeTab: string | null | undefined) => (child: (React.ReactElement<LocalJSX.IonTabButton & { onIonTabButtonClick: (e: CustomEvent) => void }>) | null | undefined) => {
@ -100,8 +101,8 @@ export const IonTabBar: React.FC<LocalJSX.IonTabBar & { currentPath?: string, na
return (
<IonTabBarUnwrapped
{...props as any}
navigate={props.navigate || ((path: string) => {
context.navigate(path);
navigate={props.navigate || ((path: string, direction: 'back' | 'none') => {
context.navigate(path, direction);
})}
currentPath={props.currentPath || context.currentPath}
>

View File

@ -1,7 +1,7 @@
import React from 'react';
import { NavContext } from '../../contexts/NavContext';
import { IonRouterOutlet } from '../proxies';
import { IonRouterOutlet } from '../IonRouterOutlet';
import { IonTabBar } from './IonTabBar';
@ -60,19 +60,11 @@ export const IonTabs = /*@__PURE__*/(() => class extends React.Component<Props>
throw new Error('IonTabs needs a IonTabBar');
}
const NavManager = this.context.getViewManager();
return (
<div style={hostStyles}>
{tabBar.props.slot === 'top' ? tabBar : null}
<div style={tabsInner} className="tabs-inner">
{this.context.hasIonicRouter() ? (
<NavManager>
{outlet}
</NavManager>
) : (
<>{outlet}</>
)}
{outlet}
</div>
{tabBar.props.slot === 'bottom' ? tabBar : null}
</div>

View File

@ -2,6 +2,7 @@ import { JSX } from '@ionic/core';
import { JSX as IoniconsJSX } from 'ionicons';
import { createReactComponent } from './createComponent';
import { HrefProps } from './hrefprops';
// ionicons
export const IonIcon = /*@__PURE__*/createReactComponent<IoniconsJSX.IonIcon, HTMLIonIconElement>('ion-icon');
@ -10,13 +11,13 @@ export const IonIcon = /*@__PURE__*/createReactComponent<IoniconsJSX.IonIcon, HT
export const IonApp = /*@__PURE__*/createReactComponent<JSX.IonApp, HTMLIonAppElement>('ion-app');
export const IonTab = /*@__PURE__*/createReactComponent<JSX.IonTab, HTMLIonTabElement>('ion-tab');
export const IonTabButton = /*@__PURE__*/createReactComponent<JSX.IonTabButton, HTMLIonTabButtonElement>('ion-tab-button');
export const IonRouterLink = /*@__PURE__*/createReactComponent<JSX.IonRouterLink, HTMLIonRouterLinkElement>('ion-router-link', true);
export const IonRouterLink = /*@__PURE__*/createReactComponent<HrefProps<JSX.IonRouterLink>, HTMLIonRouterLinkElement>('ion-router-link', true);
export const IonAvatar = /*@__PURE__*/createReactComponent<JSX.IonAvatar, HTMLIonAvatarElement>('ion-avatar');
export const IonBackdrop = /*@__PURE__*/createReactComponent<JSX.IonBackdrop, HTMLIonBackdropElement>('ion-backdrop');
export const IonBadge = /*@__PURE__*/createReactComponent<JSX.IonBadge, HTMLIonBadgeElement>('ion-badge');
export const IonButton = /*@__PURE__*/createReactComponent<JSX.IonButton, HTMLIonButtonElement>('ion-button', true);
export const IonButton = /*@__PURE__*/createReactComponent<HrefProps<JSX.IonButton>, HTMLIonButtonElement>('ion-button', true);
export const IonButtons = /*@__PURE__*/createReactComponent<JSX.IonButtons, HTMLIonButtonsElement>('ion-buttons');
export const IonCard = /*@__PURE__*/createReactComponent<JSX.IonCard, HTMLIonCardElement>('ion-card', true);
export const IonCard = /*@__PURE__*/createReactComponent<HrefProps<JSX.IonCard>, HTMLIonCardElement>('ion-card', true);
export const IonCardContent = /*@__PURE__*/createReactComponent<JSX.IonCardContent, HTMLIonCardContentElement>('ion-card-content');
export const IonCardHeader = /*@__PURE__*/createReactComponent<JSX.IonCardHeader, HTMLIonCardHeaderElement>('ion-card-header');
export const IonCardSubtitle = /*@__PURE__*/createReactComponent<JSX.IonCardSubtitle, HTMLIonCardSubtitleElement>('ion-card-subtitle');
@ -27,7 +28,7 @@ export const IonContent = /*@__PURE__*/createReactComponent<JSX.IonContent, HTML
export const IonChip = /*@__PURE__*/createReactComponent<JSX.IonChip, HTMLIonChipElement>('ion-chip');
export const IonDatetime = /*@__PURE__*/createReactComponent<JSX.IonDatetime, HTMLIonDatetimeElement>('ion-datetime');
export const IonFab = /*@__PURE__*/createReactComponent<JSX.IonFab, HTMLIonFabElement>('ion-fab');
export const IonFabButton = /*@__PURE__*/createReactComponent<JSX.IonFabButton, HTMLIonFabButtonElement>('ion-fab-button', true);
export const IonFabButton = /*@__PURE__*/createReactComponent<HrefProps<JSX.IonFabButton>, HTMLIonFabButtonElement>('ion-fab-button', true);
export const IonFabList = /*@__PURE__*/createReactComponent<JSX.IonFabList, HTMLIonFabListElement>('ion-fab-list');
export const IonFooter = /*@__PURE__*/createReactComponent<JSX.IonFooter, HTMLIonFooterElement>('ion-footer');
export const IonGrid = /*@__PURE__*/createReactComponent<JSX.IonGrid, HTMLIonGridElement>('ion-grid');
@ -36,10 +37,10 @@ export const IonImg = /*@__PURE__*/createReactComponent<JSX.IonImg, HTMLIonImgEl
export const IonInfiniteScroll = /*@__PURE__*/createReactComponent<JSX.IonInfiniteScroll, HTMLIonInfiniteScrollElement>('ion-infinite-scroll');
export const IonInfiniteScrollContent = /*@__PURE__*/createReactComponent<JSX.IonInfiniteScrollContent, HTMLIonInfiniteScrollContentElement>('ion-infinite-scroll-content');
export const IonInput = /*@__PURE__*/createReactComponent<JSX.IonInput, HTMLIonInputElement>('ion-input');
export const IonItem = /*@__PURE__*/createReactComponent<JSX.IonItem, HTMLIonItemElement>('ion-item', true);
export const IonItem = /*@__PURE__*/createReactComponent<HrefProps<JSX.IonItem>, HTMLIonItemElement>('ion-item', true);
export const IonItemDivider = /*@__PURE__*/createReactComponent<JSX.IonItemDivider, HTMLIonItemDividerElement>('ion-item-divider');
export const IonItemGroup = /*@__PURE__*/createReactComponent<JSX.IonItemGroup, HTMLIonItemGroupElement>('ion-item-group');
export const IonItemOption = /*@__PURE__*/createReactComponent<JSX.IonItemOption, HTMLIonItemOptionElement>('ion-item-option', true);
export const IonItemOption = /*@__PURE__*/createReactComponent<HrefProps<JSX.IonItemOption>, HTMLIonItemOptionElement>('ion-item-option', true);
export const IonItemOptions = /*@__PURE__*/createReactComponent<JSX.IonItemOptions, HTMLIonItemOptionsElement>('ion-item-options');
export const IonItemSliding = /*@__PURE__*/createReactComponent<JSX.IonItemSliding, HTMLIonItemSlidingElement>('ion-item-sliding');
export const IonLabel = /*@__PURE__*/createReactComponent<JSX.IonLabel, HTMLIonLabelElement>('ion-label');
@ -61,7 +62,6 @@ export const IonRefresherContent = /*@__PURE__*/createReactComponent<JSX.IonRefr
export const IonReorder = /*@__PURE__*/createReactComponent<JSX.IonReorder, HTMLIonReorderElement>('ion-reorder');
export const IonReorderGroup = /*@__PURE__*/createReactComponent<JSX.IonReorderGroup, HTMLIonReorderGroupElement>('ion-reorder-group');
export const IonRippleEffect = /*@__PURE__*/createReactComponent<JSX.IonRippleEffect, HTMLIonRippleEffectElement>('ion-ripple-effect');
export const IonRouterOutlet = /*@__PURE__*/createReactComponent<JSX.IonRouterOutlet, HTMLIonRouterOutletElement>('ion-router-outlet');
export const IonRow = /*@__PURE__*/createReactComponent<JSX.IonRow, HTMLIonRowElement>('ion-row');
export const IonSearchbar = /*@__PURE__*/createReactComponent<JSX.IonSearchbar, HTMLIonSearchbarElement>('ion-searchbar');
export const IonSegment = /*@__PURE__*/createReactComponent<JSX.IonSegment, HTMLIonSegmentElement>('ion-segment');

View File

@ -1,3 +1,5 @@
import { camelToDashCase } from './case';
export const attachEventProps = (node: HTMLElement, newProps: any, oldProps: any = {}) => {
// add any classes in className to the class list
const className = getClassName(node.classList, newProps, oldProps);
@ -6,7 +8,7 @@ export const attachEventProps = (node: HTMLElement, newProps: any, oldProps: any
}
Object.keys(newProps).forEach(name => {
if (name === 'children' || name === 'style' || name === 'ref' || name === 'className') {
if (name === 'children' || name === 'style' || name === 'ref' || name === 'class' || name === 'className' || name === 'forwardedRef') {
return;
}
if (name.indexOf('on') === 0 && name[2] === name[2].toUpperCase()) {
@ -17,7 +19,11 @@ export const attachEventProps = (node: HTMLElement, newProps: any, oldProps: any
syncEvent(node, eventNameLc, newProps[name]);
}
} else {
(node as any)[name] = newProps[name];
if (typeof newProps[name] === 'object') {
(node as any)[name] = newProps[name];
} else {
node.setAttribute(camelToDashCase(name), newProps[name]);
}
}
});
};

View File

@ -0,0 +1,2 @@
export const dashToPascalCase = (str: string) => str.toLowerCase().split('-').map(segment => segment.charAt(0).toUpperCase() + segment.slice(1)).join('');
export const camelToDashCase = (str: string) => str.replace(/([A-Z])/g, (m: string) => `-${m[0].toLowerCase()}`);

View File

@ -1,6 +1,5 @@
import { Platforms, getPlatforms as getPlatformsCore, isPlatform as isPlatformCore } from '@ionic/core';
import React from 'react';
export const dashToPascalCase = (str: string) => str.toLowerCase().split('-').map(segment => segment.charAt(0).toUpperCase() + segment.slice(1)).join('');
export type IonicReactExternalProps<PropType, ElementType> = PropType & {
ref?: React.RefObject<ElementType>;
@ -17,6 +16,7 @@ export const createForwardRef = <PropType, ElementType>(ReactComponent: any, dis
};
export * from './attachEventProps';
export * from './case';
export const isPlatform = (platform: Platforms) => {
return isPlatformCore(window, platform);

View File

@ -4,17 +4,20 @@ import React from 'react';
export interface NavContextState {
getHistory: () => History;
getLocation: () => Location;
getViewManager: () => any;
getPageManager: () => any;
getStackManager: () => any;
goBack: (defaultHref?: string) => void;
navigate: (path: string, direction?: RouterDirection) => void;
navigate: (path: string, direction?: RouterDirection | 'none') => void;
hasIonicRouter: () => boolean;
registerIonPage: (page: HTMLElement) => void;
currentPath: string | undefined;
}
export const NavContext = /*@__PURE__*/React.createContext<NavContextState>({
getHistory: () => window.history,
getLocation: () => window.location,
getViewManager: () => undefined,
getPageManager: () => undefined,
getStackManager: () => undefined,
goBack: (defaultHref?: string) => {
if (defaultHref !== undefined) {
window.location.pathname = defaultHref;
@ -24,5 +27,6 @@ export const NavContext = /*@__PURE__*/React.createContext<NavContextState>({
},
navigate: (path: string) => { window.location.pathname = path; },
hasIonicRouter: () => false,
registerIonPage: () => undefined,
currentPath: undefined
});