Skip to content

Commit be9a399

Browse files
authored
fix(popover): popover positions correctly on all frameworks (#26306)
Resolves #25337
1 parent a6c9e55 commit be9a399

File tree

5 files changed

+54
-43
lines changed

5 files changed

+54
-43
lines changed

core/src/components/popover/popover.tsx

Lines changed: 41 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,7 @@ import { iosEnterAnimation } from './animations/ios.enter';
2727
import { iosLeaveAnimation } from './animations/ios.leave';
2828
import { mdEnterAnimation } from './animations/md.enter';
2929
import { mdLeaveAnimation } from './animations/md.leave';
30-
import {
31-
configureDismissInteraction,
32-
configureKeyboardInteraction,
33-
configureTriggerInteraction,
34-
waitOneFrame,
35-
} from './utils';
30+
import { configureDismissInteraction, configureKeyboardInteraction, configureTriggerInteraction } from './utils';
3631

3732
/**
3833
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
@@ -464,40 +459,48 @@ export class Popover implements ComponentInterface, PopoverInterface {
464459
}
465460
this.configureDismissInteraction();
466461

467-
// TODO: FW-2773: Apply this to only the lazy build.
468-
/**
469-
* ionMount only needs to be emitted if the popover is inline.
470-
*/
471462
this.ionMount.emit();
472-
/**
473-
* Wait one raf before presenting the popover.
474-
* This allows the lazy build enough time to
475-
* calculate the popover dimensions for the animation.
476-
*/
477-
await waitOneFrame();
478-
479-
this.currentTransition = present(this, 'popoverEnter', iosEnterAnimation, mdEnterAnimation, {
480-
event: event || this.event,
481-
size: this.size,
482-
trigger: this.triggerEl,
483-
reference: this.reference,
484-
side: this.side,
485-
align: this.alignment,
486-
});
487-
488-
await this.currentTransition;
489-
490-
this.currentTransition = undefined;
491463

492-
/**
493-
* If popover is nested and was
494-
* presented using the "Right" arrow key,
495-
* we need to move focus to the first
496-
* descendant inside of the popover.
497-
*/
498-
if (this.focusDescendantOnPresent) {
499-
focusFirstDescendant(this.el, this.el);
500-
}
464+
return new Promise((resolve) => {
465+
/**
466+
* Wait two request animation frame loops before presenting the popover.
467+
* This allows the framework implementations enough time to mount
468+
* the popover contents, so the bounding box is set when the popover
469+
* transition starts.
470+
*
471+
* On Angular and React, a single raf is enough time, but for Vue
472+
* we need to wait two rafs. As a result we are using two rafs for
473+
* all frameworks to ensure the popover is presented correctly.
474+
*/
475+
raf(() => {
476+
raf(async () => {
477+
this.currentTransition = present(this, 'popoverEnter', iosEnterAnimation, mdEnterAnimation, {
478+
event: event || this.event,
479+
size: this.size,
480+
trigger: this.triggerEl,
481+
reference: this.reference,
482+
side: this.side,
483+
align: this.alignment,
484+
});
485+
486+
await this.currentTransition;
487+
488+
this.currentTransition = undefined;
489+
490+
/**
491+
* If popover is nested and was
492+
* presented using the "Right" arrow key,
493+
* we need to move focus to the first
494+
* descendant inside of the popover.
495+
*/
496+
if (this.focusDescendantOnPresent) {
497+
focusFirstDescendant(this.el, this.el);
498+
}
499+
500+
resolve();
501+
});
502+
});
503+
});
501504
}
502505

503506
/**

core/src/components/popover/utils.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -928,7 +928,3 @@ export const shouldShowArrow = (side: PositionSide, didAdjustBounds = false, ev?
928928

929929
return true;
930930
};
931-
932-
export const waitOneFrame = () => {
933-
return new Promise<void>((resolve) => raf(() => resolve()));
934-
};

packages/react/src/components/createInlineOverlayComponent.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,17 @@ export const createInlineOverlayComponent = <PropType, ElementType>(
5555
componentDidMount() {
5656
this.componentDidUpdate(this.props);
5757

58+
/**
59+
* Mount the inner component when the
60+
* overlay is about to open.
61+
*
62+
* For ion-popover, this is when `ionMount` is emitted.
63+
* For other overlays, this is when `willPresent` is emitted.
64+
*/
65+
this.ref.current?.addEventListener('ionMount', () => {
66+
this.setState({ isOpen: true });
67+
});
68+
5869
/**
5970
* Mount the inner component
6071
* when overlay is about to open.

packages/react/test-app/src/pages/overlay-components/PopoverComponent.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,4 @@ const PopoverComponent: React.FC = () => {
8383
);
8484
};
8585

86-
export default PopoverComponent;
86+
export default PopoverComponent;

packages/vue/src/vue-component-lib/overlays.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ export const defineOverlayContainer = <Props extends object>(name: string, defin
139139
const elementRef = ref();
140140

141141
onMounted(() => {
142+
elementRef.value.addEventListener('ion-mount', () => isOpen.value = true);
142143
elementRef.value.addEventListener('will-present', () => isOpen.value = true);
143144
elementRef.value.addEventListener('did-dismiss', () => isOpen.value = false);
144145
});

0 commit comments

Comments
 (0)