fix(react): improve component compatibility with preact (#24132)

resolves #23516
This commit is contained in:
Liam DeBeasi
2021-11-03 09:25:08 -04:00
committed by GitHub
parent ff25ac14fa
commit 15fc293d75
5 changed files with 182 additions and 180 deletions

View File

@ -95,6 +95,7 @@ export const config: Config = {
'ion-popover', 'ion-popover',
'ion-toast', 'ion-toast',
'ion-app',
'ion-icon' 'ion-icon'
] ]
}), }),

View File

@ -13,51 +13,52 @@ type Props = LocalJSX.IonApp &
ref?: React.Ref<HTMLIonAppElement>; ref?: React.Ref<HTMLIonAppElement>;
}; };
export class IonApp extends React.Component<Props> { export const IonApp = /*@__PURE__*/ (() =>
addOverlayCallback?: (id: string, overlay: any, containerElement: HTMLDivElement) => void; class extends React.Component<Props> {
removeOverlayCallback?: (id: string) => void; addOverlayCallback?: (id: string, overlay: any, containerElement: HTMLDivElement) => void;
removeOverlayCallback?: (id: string) => void;
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
} }
/* /*
Wire up methods to call into IonOverlayManager Wire up methods to call into IonOverlayManager
*/ */
ionContext: IonContextInterface = { ionContext: IonContextInterface = {
addOverlay: ( addOverlay: (
id: string, id: string,
overlay: ReactComponentOrElement, overlay: ReactComponentOrElement,
containerElement: HTMLDivElement containerElement: HTMLDivElement
) => { ) => {
if (this.addOverlayCallback) { if (this.addOverlayCallback) {
this.addOverlayCallback(id, overlay, containerElement); this.addOverlayCallback(id, overlay, containerElement);
} }
}, },
removeOverlay: (id: string) => { removeOverlay: (id: string) => {
if (this.removeOverlayCallback) { if (this.removeOverlayCallback) {
this.removeOverlayCallback(id); this.removeOverlayCallback(id);
} }
}, },
}; };
render() { render() {
return ( return (
<IonContext.Provider value={this.ionContext}> <IonContext.Provider value={this.ionContext}>
<IonAppInner {...this.props}>{this.props.children}</IonAppInner> <IonAppInner {...this.props}>{this.props.children}</IonAppInner>
<IonOverlayManager <IonOverlayManager
onAddOverlay={(callback) => { onAddOverlay={(callback) => {
this.addOverlayCallback = callback; this.addOverlayCallback = callback;
}} }}
onRemoveOverlay={(callback) => { onRemoveOverlay={(callback) => {
this.removeOverlayCallback = callback; this.removeOverlayCallback = callback;
}} }}
/> />
</IonContext.Provider> </IonContext.Provider>
); );
} }
static get displayName() { static get displayName() {
return 'IonApp'; return 'IonApp';
} }
} })();

View File

@ -12,37 +12,38 @@ type Props = LocalJSX.IonTabButton &
onClick?: (e: any) => void; onClick?: (e: any) => void;
}; };
export class IonTabButton extends React.Component<Props> { export const IonTabButton = /*@__PURE__*/ (() =>
constructor(props: Props) { class extends React.Component<Props> {
super(props); constructor(props: Props) {
this.handleIonTabButtonClick = this.handleIonTabButtonClick.bind(this); super(props);
} this.handleIonTabButtonClick = this.handleIonTabButtonClick.bind(this);
}
handleIonTabButtonClick() { handleIonTabButtonClick() {
if (this.props.onClick) { if (this.props.onClick) {
this.props.onClick( this.props.onClick(
new CustomEvent('ionTabButtonClick', { new CustomEvent('ionTabButtonClick', {
detail: { detail: {
tab: this.props.tab, tab: this.props.tab,
href: this.props.href, href: this.props.href,
routeOptions: this.props.routerOptions, routeOptions: this.props.routerOptions,
}, },
}) })
);
}
}
render() {
const { onClick, ...rest } = this.props;
return (
<IonTabButtonInner
onIonTabButtonClick={this.handleIonTabButtonClick}
{...rest}
></IonTabButtonInner>
); );
} }
}
render() { static get displayName() {
const { onClick, ...rest } = this.props; return 'IonTabButton';
return ( }
<IonTabButtonInner })();
onIonTabButtonClick={this.handleIonTabButtonClick}
{...rest}
></IonTabButtonInner>
);
}
static get displayName() {
return 'IonTabButton';
}
}

View File

@ -56,125 +56,126 @@ const tabsInner: React.CSSProperties = {
contain: 'layout size style', contain: 'layout size style',
}; };
export class IonTabs extends React.Component<Props> { export const IonTabs = /*@__PURE__*/ (() =>
context!: React.ContextType<typeof NavContext>; class extends React.Component<Props> {
routerOutletRef: React.Ref<HTMLIonRouterOutletElement> = React.createRef(); context!: React.ContextType<typeof NavContext>;
selectTabHandler?: (tag: string) => boolean; routerOutletRef: React.Ref<HTMLIonRouterOutletElement> = React.createRef();
tabBarRef = React.createRef<any>(); selectTabHandler?: (tag: string) => boolean;
tabBarRef = React.createRef<any>();
ionTabContextState: IonTabsContextState = { ionTabContextState: IonTabsContextState = {
activeTab: undefined, activeTab: undefined,
selectTab: () => false, selectTab: () => false,
}; };
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
}
componentDidMount() {
if (this.tabBarRef.current) {
// Grab initial value
this.ionTabContextState.activeTab = this.tabBarRef.current.state.activeTab;
// Override method
this.tabBarRef.current.setActiveTabOnContext = (tab: string) => {
this.ionTabContextState.activeTab = tab;
};
this.ionTabContextState.selectTab = this.tabBarRef.current.selectTab;
} }
}
render() { componentDidMount() {
let outlet: React.ReactElement<{}> | undefined; if (this.tabBarRef.current) {
let tabBar: React.ReactElement | undefined; // Grab initial value
const { className, onIonTabsDidChange, onIonTabsWillChange, ...props } = this.props; this.ionTabContextState.activeTab = this.tabBarRef.current.state.activeTab;
// Override method
const children = this.tabBarRef.current.setActiveTabOnContext = (tab: string) => {
typeof this.props.children === 'function' this.ionTabContextState.activeTab = tab;
? (this.props.children as ChildFunction)(this.ionTabContextState) };
: this.props.children; this.ionTabContextState.selectTab = this.tabBarRef.current.selectTab;
React.Children.forEach(children, (child: any) => {
if (child == null || typeof child !== 'object' || !child.hasOwnProperty('type')) {
return;
}
if (child.type === IonRouterOutlet || child.type.isRouterOutlet) {
outlet = React.cloneElement(child, { tabs: true });
} else if (child.type === Fragment && child.props.children[0].type === IonRouterOutlet) {
outlet = child.props.children[0];
} }
}
let childProps: any = { render() {
ref: this.tabBarRef let outlet: React.ReactElement<{}> | undefined;
} let tabBar: React.ReactElement | undefined;
const { className, onIonTabsDidChange, onIonTabsWillChange, ...props } = this.props;
/** const children =
* Only pass these props typeof this.props.children === 'function'
* down from IonTabs to IonTabBar ? (this.props.children as ChildFunction)(this.ionTabContextState)
* if they are defined, otherwise : this.props.children;
* if you have a handler set on
* IonTabBar it will be overridden. React.Children.forEach(children, (child: any) => {
*/ if (child == null || typeof child !== 'object' || !child.hasOwnProperty('type')) {
if (onIonTabsDidChange !== undefined) { return;
childProps = {
...childProps,
onIonTabsDidChange
} }
} if (child.type === IonRouterOutlet || child.type.isRouterOutlet) {
outlet = React.cloneElement(child, { tabs: true });
if (onIonTabsWillChange !== undefined) { } else if (child.type === Fragment && child.props.children[0].type === IonRouterOutlet) {
childProps = { outlet = child.props.children[0];
...childProps,
onIonTabsWillChange
} }
let childProps: any = {
ref: this.tabBarRef
}
/**
* Only pass these props
* down from IonTabs to IonTabBar
* if they are defined, otherwise
* if you have a handler set on
* IonTabBar it will be overridden.
*/
if (onIonTabsDidChange !== undefined) {
childProps = {
...childProps,
onIonTabsDidChange
}
}
if (onIonTabsWillChange !== undefined) {
childProps = {
...childProps,
onIonTabsWillChange
}
}
if (child.type === IonTabBar || child.type.isTabBar) {
tabBar = React.cloneElement(child, childProps);
} else if (
child.type === Fragment &&
(child.props.children[1].type === IonTabBar || child.props.children[1].type.isTabBar)
) {
tabBar = React.cloneElement(child.props.children[1], childProps);
}
});
if (!outlet) {
throw new Error('IonTabs must contain an IonRouterOutlet');
}
if (!tabBar) {
throw new Error('IonTabs needs a IonTabBar');
} }
if (child.type === IonTabBar || child.type.isTabBar) { return (
tabBar = React.cloneElement(child, childProps); <IonTabsContext.Provider value={this.ionTabContextState}>
} else if ( {this.context.hasIonicRouter() ? (
child.type === Fragment && <PageManager
(child.props.children[1].type === IonTabBar || child.props.children[1].type.isTabBar) className={className ? `${className}` : ''}
) { routeInfo={this.context.routeInfo}
tabBar = React.cloneElement(child.props.children[1], childProps); {...props}
} >
}); <ion-tabs className="ion-tabs" style={hostStyles}>
{tabBar.props.slot === 'top' ? tabBar : null}
if (!outlet) { <div style={tabsInner} className="tabs-inner">
throw new Error('IonTabs must contain an IonRouterOutlet'); {outlet}
} </div>
if (!tabBar) { {tabBar.props.slot === 'bottom' ? tabBar : null}
throw new Error('IonTabs needs a IonTabBar'); </ion-tabs>
} </PageManager>
) : (
return ( <div className={className ? `${className}` : 'ion-tabs'} {...props} style={hostStyles}>
<IonTabsContext.Provider value={this.ionTabContextState}>
{this.context.hasIonicRouter() ? (
<PageManager
className={className ? `${className}` : ''}
routeInfo={this.context.routeInfo}
{...props}
>
<ion-tabs className="ion-tabs" style={hostStyles}>
{tabBar.props.slot === 'top' ? tabBar : null} {tabBar.props.slot === 'top' ? tabBar : null}
<div style={tabsInner} className="tabs-inner"> <div style={tabsInner} className="tabs-inner">
{outlet} {outlet}
</div> </div>
{tabBar.props.slot === 'bottom' ? tabBar : null} {tabBar.props.slot === 'bottom' ? tabBar : null}
</ion-tabs>
</PageManager>
) : (
<div className={className ? `${className}` : 'ion-tabs'} {...props} style={hostStyles}>
{tabBar.props.slot === 'top' ? tabBar : null}
<div style={tabsInner} className="tabs-inner">
{outlet}
</div> </div>
{tabBar.props.slot === 'bottom' ? tabBar : null} )}
</div> </IonTabsContext.Provider>
)} );
</IonTabsContext.Provider> }
);
}
static get contextType() { static get contextType() {
return NavContext; return NavContext;
} }
} })();

View File

@ -7,7 +7,6 @@ import type { JSX } from '@ionic/core/components';
import { IonAccordion as IonAccordionCmp } from '@ionic/core/components/ion-accordion.js'; import { IonAccordion as IonAccordionCmp } from '@ionic/core/components/ion-accordion.js';
import { IonAccordionGroup as IonAccordionGroupCmp } from '@ionic/core/components/ion-accordion-group.js'; import { IonAccordionGroup as IonAccordionGroupCmp } from '@ionic/core/components/ion-accordion-group.js';
import { IonApp as IonAppCmp } from '@ionic/core/components/ion-app.js';
import { IonAvatar as IonAvatarCmp } from '@ionic/core/components/ion-avatar.js'; import { IonAvatar as IonAvatarCmp } from '@ionic/core/components/ion-avatar.js';
import { IonBackdrop as IonBackdropCmp } from '@ionic/core/components/ion-backdrop.js'; import { IonBackdrop as IonBackdropCmp } from '@ionic/core/components/ion-backdrop.js';
import { IonBadge as IonBadgeCmp } from '@ionic/core/components/ion-badge.js'; import { IonBadge as IonBadgeCmp } from '@ionic/core/components/ion-badge.js';
@ -76,7 +75,6 @@ import { IonVirtualScroll as IonVirtualScrollCmp } from '@ionic/core/components/
export const IonAccordion = /*@__PURE__*/createReactComponent<JSX.IonAccordion, HTMLIonAccordionElement>('ion-accordion', undefined, undefined, IonAccordionCmp); export const IonAccordion = /*@__PURE__*/createReactComponent<JSX.IonAccordion, HTMLIonAccordionElement>('ion-accordion', undefined, undefined, IonAccordionCmp);
export const IonAccordionGroup = /*@__PURE__*/createReactComponent<JSX.IonAccordionGroup, HTMLIonAccordionGroupElement>('ion-accordion-group', undefined, undefined, IonAccordionGroupCmp); export const IonAccordionGroup = /*@__PURE__*/createReactComponent<JSX.IonAccordionGroup, HTMLIonAccordionGroupElement>('ion-accordion-group', undefined, undefined, IonAccordionGroupCmp);
export const IonApp = /*@__PURE__*/createReactComponent<JSX.IonApp, HTMLIonAppElement>('ion-app', undefined, undefined, IonAppCmp);
export const IonAvatar = /*@__PURE__*/createReactComponent<JSX.IonAvatar, HTMLIonAvatarElement>('ion-avatar', undefined, undefined, IonAvatarCmp); export const IonAvatar = /*@__PURE__*/createReactComponent<JSX.IonAvatar, HTMLIonAvatarElement>('ion-avatar', undefined, undefined, IonAvatarCmp);
export const IonBackdrop = /*@__PURE__*/createReactComponent<JSX.IonBackdrop, HTMLIonBackdropElement>('ion-backdrop', undefined, undefined, IonBackdropCmp); export const IonBackdrop = /*@__PURE__*/createReactComponent<JSX.IonBackdrop, HTMLIonBackdropElement>('ion-backdrop', undefined, undefined, IonBackdropCmp);
export const IonBadge = /*@__PURE__*/createReactComponent<JSX.IonBadge, HTMLIonBadgeElement>('ion-badge', undefined, undefined, IonBadgeCmp); export const IonBadge = /*@__PURE__*/createReactComponent<JSX.IonBadge, HTMLIonBadgeElement>('ion-badge', undefined, undefined, IonBadgeCmp);