mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-19 11:41:20 +08:00
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:
@ -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;
|
||||
}
|
||||
})();
|
||||
|
46
packages/react/src/components/IonRouterOutlet.tsx
Normal file
46
packages/react/src/components/IonRouterOutlet.tsx
Normal 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');
|
@ -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> {
|
||||
|
5
packages/react/src/components/hrefprops.ts
Normal file
5
packages/react/src/components/hrefprops.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export declare type RouterDirection = 'forward' | 'back' | 'none';
|
||||
|
||||
export type HrefProps<T> = Omit<T, 'routerDirection'> & {
|
||||
routerDirection?: RouterDirection;
|
||||
};
|
@ -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({
|
||||
|
@ -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');
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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}
|
||||
>
|
||||
|
@ -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>
|
||||
|
@ -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');
|
||||
|
@ -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]);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
2
packages/react/src/components/utils/case.ts
Normal file
2
packages/react/src/components/utils/case.ts
Normal 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()}`);
|
@ -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);
|
||||
|
@ -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
|
||||
});
|
||||
|
Reference in New Issue
Block a user