@ionic/react 8.8.8-nightly.20260519 → 8.8.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +78 -9
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -839,6 +839,14 @@ const detachEvent = (node, eventName) => {
|
|
|
839
839
|
}
|
|
840
840
|
};
|
|
841
841
|
|
|
842
|
+
/**
|
|
843
|
+
* Set to `true` when rendering inside another inline overlay. Nested
|
|
844
|
+
* overlays render at their JSX position (no portal) so that core's
|
|
845
|
+
* `el.closest('ion-popover')`-style nesting detection keeps working,
|
|
846
|
+
* and the outer overlay's portal already gives the subtree the correct
|
|
847
|
+
* React event-delegation root.
|
|
848
|
+
*/
|
|
849
|
+
const NestedOverlayContext = React.createContext(false);
|
|
842
850
|
const createInlineOverlayComponent = (tagName, defineCustomElement, hasDelegateHost) => {
|
|
843
851
|
if (defineCustomElement) {
|
|
844
852
|
defineCustomElement();
|
|
@@ -847,6 +855,7 @@ const createInlineOverlayComponent = (tagName, defineCustomElement, hasDelegateH
|
|
|
847
855
|
const ReactComponent = class extends React.Component {
|
|
848
856
|
constructor(props) {
|
|
849
857
|
super(props);
|
|
858
|
+
this.isUnmounted = false;
|
|
850
859
|
this.handleIonMount = () => {
|
|
851
860
|
/**
|
|
852
861
|
* Mount the inner component when the
|
|
@@ -890,12 +899,45 @@ const createInlineOverlayComponent = (tagName, defineCustomElement, hasDelegateH
|
|
|
890
899
|
this.state = { isOpen: false };
|
|
891
900
|
// Create a local ref to the inner child element.
|
|
892
901
|
this.wrapperRef = React.createRef();
|
|
902
|
+
// Marker stays at the JSX location so we can recover the immediate
|
|
903
|
+
// JSX parent after the overlay has been portaled to ion-app.
|
|
904
|
+
this.markerRef = React.createRef();
|
|
905
|
+
/**
|
|
906
|
+
* Resolve the portal target to the same container CoreDelegate
|
|
907
|
+
* teleports overlays into. Portaling here keeps the overlay inside
|
|
908
|
+
* React's tree so React's synthetic events still dispatch to its
|
|
909
|
+
* children, even after CoreDelegate moves the DOM node out of the
|
|
910
|
+
* declared JSX parent.
|
|
911
|
+
*/
|
|
912
|
+
this.portalTarget = typeof document !== 'undefined' ? document.querySelector('ion-app') || document.body : null;
|
|
893
913
|
}
|
|
894
914
|
componentDidMount() {
|
|
915
|
+
// Reset for React 18 StrictMode: the dev-mode unmount/remount cycle
|
|
916
|
+
// re-uses this instance and leaves the flag set from the prior
|
|
917
|
+
// componentWillUnmount.
|
|
918
|
+
this.isUnmounted = false;
|
|
895
919
|
this.componentDidUpdate(this.props);
|
|
896
920
|
this.ref.current?.addEventListener('ionMount', this.handleIonMount);
|
|
897
921
|
this.ref.current?.addEventListener('willPresent', this.handleWillPresent);
|
|
898
922
|
this.ref.current?.addEventListener('didDismiss', this.handleDidDismiss);
|
|
923
|
+
/**
|
|
924
|
+
* The overlay is portaled to `portalTarget`, so Stencil caches that
|
|
925
|
+
* container as `cachedOriginalParent`. Modal features (sheet
|
|
926
|
+
* child-route passthrough, parent-removal auto-dismiss) walk up
|
|
927
|
+
* from `cachedOriginalParent` to find the enclosing `.ion-page`,
|
|
928
|
+
* so we redirect it at the marker's JSX parent.
|
|
929
|
+
*/
|
|
930
|
+
const overlay = this.ref.current;
|
|
931
|
+
if (overlay) {
|
|
932
|
+
componentOnReady(overlay, () => {
|
|
933
|
+
if (this.isUnmounted)
|
|
934
|
+
return;
|
|
935
|
+
const markerParent = this.markerRef.current?.parentElement ?? null;
|
|
936
|
+
if (markerParent && markerParent !== this.portalTarget) {
|
|
937
|
+
overlay.cachedOriginalParent = markerParent;
|
|
938
|
+
}
|
|
939
|
+
});
|
|
940
|
+
}
|
|
899
941
|
}
|
|
900
942
|
componentDidUpdate(prevProps) {
|
|
901
943
|
const node = this.ref.current;
|
|
@@ -905,10 +947,11 @@ const createInlineOverlayComponent = (tagName, defineCustomElement, hasDelegateH
|
|
|
905
947
|
* so they don't get attached twice and called twice.
|
|
906
948
|
*/
|
|
907
949
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
908
|
-
const { onDidDismiss, onWillPresent, ...cProps } = this.props;
|
|
950
|
+
const { onDidDismiss, onWillPresent, isNested, ...cProps } = this.props;
|
|
909
951
|
attachProps(node, cProps, prevProps);
|
|
910
952
|
}
|
|
911
953
|
componentWillUnmount() {
|
|
954
|
+
this.isUnmounted = true;
|
|
912
955
|
const node = this.ref.current;
|
|
913
956
|
/**
|
|
914
957
|
* If the overlay is being unmounted, but is still
|
|
@@ -932,13 +975,28 @@ const createInlineOverlayComponent = (tagName, defineCustomElement, hasDelegateH
|
|
|
932
975
|
* avoid memory leaks.
|
|
933
976
|
*/
|
|
934
977
|
node.removeEventListener('didDismiss', this.handleDidDismiss);
|
|
935
|
-
|
|
978
|
+
if (this.props.isNested) {
|
|
979
|
+
/**
|
|
980
|
+
* Nested overlays render inline (no portal). CoreDelegate may
|
|
981
|
+
* have moved the node out of its React parent, so React's
|
|
982
|
+
* unmount won't reach it. Remove it directly.
|
|
983
|
+
*/
|
|
984
|
+
node.remove();
|
|
985
|
+
}
|
|
986
|
+
else if (node.isConnected && this.portalTarget && node.parentNode !== this.portalTarget) {
|
|
987
|
+
/**
|
|
988
|
+
* Portaled path: move the overlay back into `portalTarget` so
|
|
989
|
+
* React's portal removeChild can find it. CoreDelegate (or user
|
|
990
|
+
* code in onWillPresent) may have moved it elsewhere while open.
|
|
991
|
+
*/
|
|
992
|
+
this.portalTarget.appendChild(node);
|
|
993
|
+
}
|
|
936
994
|
detachProps(node, this.props);
|
|
937
995
|
}
|
|
938
996
|
}
|
|
939
997
|
render() {
|
|
940
998
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
941
|
-
const { children, forwardedRef, style, className, ref, ...cProps } = this.props;
|
|
999
|
+
const { children, forwardedRef, style, className, ref, isNested, ...cProps } = this.props;
|
|
942
1000
|
const propsToPass = Object.keys(cProps).reduce((acc, name) => {
|
|
943
1001
|
if (name.indexOf('on') === 0 && name[2] === name[2].toUpperCase()) {
|
|
944
1002
|
const eventName = name.substring(2).toLowerCase();
|
|
@@ -966,12 +1024,12 @@ const createInlineOverlayComponent = (tagName, defineCustomElement, hasDelegateH
|
|
|
966
1024
|
}
|
|
967
1025
|
return DELEGATE_HOST;
|
|
968
1026
|
};
|
|
969
|
-
|
|
1027
|
+
const overlayElement = createElement(tagName, newProps,
|
|
1028
|
+
// Children, not the overlay host, observe `isNested = true`.
|
|
1029
|
+
createElement(NestedOverlayContext.Provider, { value: true },
|
|
970
1030
|
/**
|
|
971
|
-
* We only want the inner component
|
|
972
|
-
*
|
|
973
|
-
* so conditionally render the component
|
|
974
|
-
* based on the isOpen state.
|
|
1031
|
+
* We only want the inner component to be mounted if the overlay
|
|
1032
|
+
* is open, so conditionally render based on `isOpen` state.
|
|
975
1033
|
*/
|
|
976
1034
|
this.state.isOpen || this.props.keepContentsMounted
|
|
977
1035
|
? createElement('div', {
|
|
@@ -979,12 +1037,23 @@ const createInlineOverlayComponent = (tagName, defineCustomElement, hasDelegateH
|
|
|
979
1037
|
className: getWrapperClasses(),
|
|
980
1038
|
}, children)
|
|
981
1039
|
: null));
|
|
1040
|
+
// Top-level overlays portal into `portalTarget` with a marker
|
|
1041
|
+
// `<template>` at the JSX location to recover the immediate JSX
|
|
1042
|
+
// parent after CoreDelegate teleports. Nested overlays and SSR
|
|
1043
|
+
// fall back to a `<template>` wrapper.
|
|
1044
|
+
if (!isNested && this.portalTarget) {
|
|
1045
|
+
return createElement(React.Fragment, null, createElement('template', { ref: this.markerRef }), createPortal(overlayElement, this.portalTarget));
|
|
1046
|
+
}
|
|
1047
|
+
return createElement('template', {}, overlayElement);
|
|
982
1048
|
}
|
|
983
1049
|
static get displayName() {
|
|
984
1050
|
return displayName;
|
|
985
1051
|
}
|
|
986
1052
|
};
|
|
987
|
-
|
|
1053
|
+
// Forward the nesting context as a prop to avoid contextType on the class.
|
|
1054
|
+
const ReactComponentWithNesting = (props) => createElement(NestedOverlayContext.Consumer, null, (isNested) => createElement(ReactComponent, { ...props, isNested }));
|
|
1055
|
+
ReactComponentWithNesting.displayName = displayName;
|
|
1056
|
+
return createForwardRef(ReactComponentWithNesting, displayName);
|
|
988
1057
|
};
|
|
989
1058
|
const DELEGATE_HOST = 'ion-delegate-host';
|
|
990
1059
|
|