mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
chore(tests): fixing issue with dynamic classname test
This commit is contained in:
@@ -818,7 +818,7 @@ export class StackManager extends React.PureComponent<StackManagerProps> {
|
||||
/**
|
||||
* DO NOT remove ion-page-invisible here.
|
||||
*
|
||||
* PageManager.render() adds ion-page-invisible to prevent flash before componentDidMount.
|
||||
* PageManager's ref callback adds ion-page-invisible synchronously to prevent flash.
|
||||
* At this point, the <IonPage> div exists but its CHILDREN (header, toolbar, menu-button)
|
||||
* have NOT rendered yet. If we remove ion-page-invisible now, the page becomes visible
|
||||
* with empty/incomplete content, causing a flicker (especially for ion-menu-button which
|
||||
|
||||
@@ -1,52 +1,46 @@
|
||||
import {
|
||||
IonButton,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonRouterOutlet,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
} from '@ionic/react';
|
||||
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from '@ionic/react';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { Route } from 'react-router';
|
||||
|
||||
/**
|
||||
* Test page for verifying that dynamically changing className on IonPage
|
||||
* preserves framework-added classes (can-go-back, ion-page-invisible, etc.).
|
||||
*
|
||||
* Related issue: https://github.com/ionic-team/ionic-framework/issues/22631
|
||||
*/
|
||||
const DynamicIonpageClassnames: React.FC = () => {
|
||||
return (
|
||||
<IonRouterOutlet>
|
||||
<Route path="/dynamic-ionpage-classnames" element={<Page />} />
|
||||
</IonRouterOutlet>
|
||||
);
|
||||
return <Page />;
|
||||
};
|
||||
|
||||
export default DynamicIonpageClassnames;
|
||||
|
||||
const Page: React.FC = (props) => {
|
||||
const Page: React.FC = () => {
|
||||
const [styleClass, setStyleClass] = useState('initial-class');
|
||||
const [divClasses, setDivClasses] = useState<string>();
|
||||
const ref = useRef<HTMLDivElement>();
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let observer: MutationObserver | undefined;
|
||||
if(ref.current) {
|
||||
observer = new MutationObserver(function (event) {
|
||||
setDivClasses(ref.current?.className)
|
||||
})
|
||||
if (!ref.current) return;
|
||||
|
||||
observer.observe(ref.current, {
|
||||
attributes: true,
|
||||
attributeFilter: ['class'],
|
||||
childList: false,
|
||||
characterData: false
|
||||
})
|
||||
}
|
||||
return () => observer?.disconnect()
|
||||
}, [])
|
||||
const observer = new MutationObserver(() => {
|
||||
setDivClasses(ref.current?.className);
|
||||
});
|
||||
|
||||
observer.observe(ref.current, {
|
||||
attributes: true,
|
||||
attributeFilter: ['class'],
|
||||
childList: false,
|
||||
characterData: false,
|
||||
});
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<IonPage className={styleClass} ref={ref} data-pageid="dynamic-ionpage-classnames">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Dynamic Ionpage Classnames</IonTitle>
|
||||
<button slot="end" onClick={() => setStyleClass('other-class')}>Add Class</button>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent className="ion-padding">
|
||||
@@ -59,7 +53,6 @@ const Page: React.FC = (props) => {
|
||||
both "other-class" AND "ion-page". If only "other-class" appears,
|
||||
the framework classes were incorrectly removed.
|
||||
</p>
|
||||
<IonButton onClick={() => setStyleClass('other-class')}>Add Class</IonButton>
|
||||
<p>Current classes: {divClasses}</p>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
|
||||
@@ -22,7 +22,18 @@ export class PageManager extends React.PureComponent<PageManagerProps> {
|
||||
super(props);
|
||||
this.ionPageElementRef = React.createRef();
|
||||
// React refs must be stable (not created inline).
|
||||
this.stableMergedRefs = mergeRefs(this.ionPageElementRef, this.props.forwardedRef);
|
||||
// Wrap merged refs to add ion-page-invisible synchronously when element is created
|
||||
const baseMergedRefs = mergeRefs(this.ionPageElementRef, this.props.forwardedRef);
|
||||
this.stableMergedRefs = (node: HTMLDivElement | null) => {
|
||||
if (node && !node.classList.contains('ion-page-invisible') && !node.classList.contains('ion-page-hidden')) {
|
||||
// Add ion-page-invisible synchronously before first paint (if in an outlet)
|
||||
// This prevents the flash that occurs when componentDidMount runs after paint
|
||||
if (this.context?.isInOutlet?.()) {
|
||||
node.classList.add('ion-page-invisible');
|
||||
}
|
||||
}
|
||||
baseMergedRefs(node);
|
||||
};
|
||||
|
||||
/**
|
||||
* This binds the scope of the following methods to the class scope.
|
||||
@@ -66,16 +77,11 @@ export class PageManager extends React.PureComponent<PageManagerProps> {
|
||||
|
||||
componentDidMount() {
|
||||
if (this.ionPageElementRef.current) {
|
||||
// Add user classes via classList to preserve framework-added classes on re-renders
|
||||
if (this.props.className) {
|
||||
this.parseClasses(this.props.className).forEach((cls) => {
|
||||
this.ionPageElementRef.current!.classList.add(cls);
|
||||
});
|
||||
}
|
||||
|
||||
// Note: ion-page-invisible is now added in render() to prevent flash.
|
||||
// We no longer add it here to avoid race conditions where the browser
|
||||
// paints the visible element before componentDidMount runs.
|
||||
// Add user classes via DOM manipulation to preserve framework-added classes.
|
||||
// We only set "ion-page" in JSX; user classes are added here.
|
||||
// Note: ion-page-invisible is added in the ref callback (stableMergedRefs) to prevent flash.
|
||||
// The ref callback runs synchronously when the element is created, before the browser paints.
|
||||
this.updateUserClasses(undefined, this.props.className);
|
||||
|
||||
this.context.registerIonPage(this.ionPageElementRef.current, this.props.routeInfo!);
|
||||
this.ionPageElementRef.current.addEventListener('ionViewWillEnter', this.ionViewWillEnterHandler);
|
||||
@@ -126,21 +132,14 @@ export class PageManager extends React.PureComponent<PageManagerProps> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { className, children, routeInfo, forwardedRef, ...props } = this.props;
|
||||
|
||||
/**
|
||||
* Start with ion-page-invisible when inside an outlet to prevent flash.
|
||||
* Previously, ion-page-invisible was added in componentDidMount, but the browser
|
||||
* could paint the visible element before componentDidMount runs, causing a flash.
|
||||
* The invisible class is removed by the StackManager when the page becomes active.
|
||||
*/
|
||||
const isInOutlet = this.context?.isInOutlet?.() ?? false;
|
||||
const initialClassName = isInOutlet ? 'ion-page ion-page-invisible' : 'ion-page';
|
||||
|
||||
// Only set "ion-page" in JSX. User classes are managed via DOM in componentDidMount/componentDidUpdate
|
||||
// to preserve framework-added classes (can-go-back, ion-page-invisible, etc.) when className prop changes.
|
||||
return (
|
||||
<IonLifeCycleContext.Consumer>
|
||||
{(context) => {
|
||||
this.ionLifeCycleContext = context;
|
||||
return (
|
||||
<div className={initialClassName} ref={this.stableMergedRefs} {...props}>
|
||||
<div className="ion-page" ref={this.stableMergedRefs} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user